diff --git a/CLAUDE.md b/CLAUDE.md index 1ca68f0..47ea9de 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -105,6 +105,18 @@ GW je VLAN = `.1` (crs326-core L3) · Mgmt VLAN99: core `.99.1`, branch `.99.2`, ## Kritische Gotchas - 📜 **~~ALLNET-Gotchas (1 Telnet-VTY, `save` nicht vergessen)~~ — gegenstandslos seit 2026-08-08** (Gerät ausgebaut). Nur noch historisch relevant, s. `switch/README.md`. +- 📝 **WLAN-Logs sind seit 2026-08-30 persistent — und der Topic heißt `wireless`, nicht `wifi`.** + Auch auf dem neuen `wifi-qcom`-Stack tragen Roaming-/Connect-Events `wireless;info` bzw. + `wireless,debug`. Ein geratenes `topics=wifi` wäre wirkungslos geblieben. Zwei Regeln auf dem + **Core** (nicht auf den CAPs — unter CAPsMAN führt der Manager die Events) schicken `wireless` + und `caps` in denselben Kollektor wie TP5 (`10.40.20.18:514`, Tagesdateien unter + `/opt/tp5-syslog/log/10.40.20.1/`). ⚠️ **RouterOS UND-verknüpft Topics innerhalb EINER Regel** + (`topics=info,!firewall` = info UND NICHT firewall) — `topics=wireless,caps` hätte **nichts** + gematcht, es braucht zwei Regeln. Vorher lief alles nur in einen ~75-Zeilen-Ringpuffer, der von + einer Stunde Arbeit überschrieben war; ein seit Wochen gemeldetes Client-Problem war deshalb + **rückwirkend nicht diagnostizierbar**. `/system logging` steht jetzt unter der 27. ComplianceRule + `system-logging` — vorher unter keiner. **Merkregel: bevor man ein sporadisches Problem sucht, + erst prüfen, ob es überhaupt irgendwo mitgeschrieben wird.** - 🔑 **CAPsMAN verteilt KEINE Login-Passwörter — nur WLAN-PSKs.** Zwei verschiedene Dinge heißen „Passwort": die **WPA-Passphrase** (`/interface wifi security`) liegt am Manager und wird an die CAPs verteilt ✅; der **Geräte-Login** (`/user`) ist eine **lokale Benutzer-DB auf jedem Gerät** ❌. diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index d40c665..0593359 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -105,9 +105,14 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d in Nautobot *Decommissioned*); Omada-Controller + `omada-mcp` gelöscht (2026-07-16); FritzBox 6660 raus seit 2026-07-01 (Liwest-Tausch auf Arris). **VLAN1 ist leer** (`/ip arp` auf `bridge` = 0 Einträge, 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), +- 🆕 **WLAN-Logging persistent seit 2026-08-30 (TP25 Fund E):** `wireless` + `caps` gehen vom Core + in den TP5-Kollektor (`10.40.20.18:514`); `/system logging` steht unter der **27. ComplianceRule** + `system-logging`. Vorher nur Ringpuffer → ein seit Wochen gemeldeter iPhone-Abbruch war rückwirkend + nicht diagnostizierbar. **Fund E ist wieder offen**, Funk-Parameter werden bis zur Datengrundlage + bewusst **nicht** angefasst. +- **Kontrolle / Governance:** MikroMCP für Reads/Debug (alle 5 Geräte in der Registry), **Config-Änderungen ausschließlich über Nautobot Golden Config** (TP21/TP22 — Deploy-Pipeline scharf, - **104/104 Compliance-Checks grün, 0 Drifts** bei **26 Rules × 4 Geräten** — live gegen die API + **135/135 Compliance-Checks grün, 0 Drifts** bei **27 Rules × 5 Geräten** (Stand 2026-08-30) — live gegen die API geprüft am **2026-08-23**; zuletzt dazugekommen: **`wifi-access-list`** (08-22), **`queue`** (08-23, TP6) und **`ip-firewall-mangle`** (08-23, TP6.1), s. TP22-Nachtrag im Archiv). *(Hier stand bis zum 08-23 „100/100 bei 25 Rules" — eine Zählung, die TP6.1 am selben Tag überholt @@ -1301,11 +1306,60 @@ darf, *ohne* dass die gemessene Schwäche unangetastet bleibt. - **Wenn später doch der Outdoor-AP dazukommt:** er gehört nach **Band C** (30 dBm, outdoor zulässig); Band A/B sind dann von den drei Indoor-APs belegt und die Ausweichspur `5220/Ce` ist aufgebraucht. -**Fund E (`signal-range`-Boden am Manager) — abgestuft, NICHT umsetzen.** Der Sticky-Client vom 08-20 -reproduzierte sich am 08-22 nicht (Treppe: sauberer Wechsel ap2→ap1, 0 % über 57 s). Und auf der -Terrasse würde ein Boden **schaden**, weil es dort keinen besseren AP gibt, auf den gesteuert werden -könnte. ⇒ **Erst wieder aufgreifen, wenn ein Sticky-Fall erneut gemessen ist.** -*(Der Defekt ist nicht gegenstandslos, nur unbelegt: Fund B/C wurden am 08-20 mit ap1 auf `ether15` +#### 🔴 Fund E — **WIEDER GEÖFFNET 2026-08-30: die Wiedervorlage-Bedingung ist eingetreten** + +Fund E war „abgestuft, NICHT umsetzen" mit einer ausdrücklichen Bedingung: *„Erst wieder +aufgreifen, wenn ein Sticky-Fall erneut gemessen ist."* Am **2026-08-30** ist genau das passiert. + +**Auslöser war eine unabhängige User-Meldung**, nicht die Suche danach: *„die letzten Tage/Wochen +regelmäßig mit dem iPhone den WiFi-Link verloren obwohl bester Kontakt. Ich lande auf 5G und nicht +am alten AP oder dem anderen."* 🔑 **Auf Mobilfunk zu fallen ist kein Roaming-Problem** — bei +schlechtem Roaming bliebe das Telefon im WLAN und wäre nur langsam. Es heißt: die Assoziation ist +gestorben und die Neuanmeldung gescheitert. + +**Neue Messung (Manager-Log, 2026-08-30):** dasselbe iPhone `F2:62:4C:E1:AF:09` machte **16 +Bandwechsel in 20 Minuten** zwischen `cap-wifi5` (ap3, 5 GHz) und `cap-wifi6` (ap3, 2,4 GHz), +zweimal im Abstand von **einer Sekunde**, bei **−53 … −81 dBm**. Kurz darauf dasselbe zwischen +`cap-wifi1`↔`cap-wifi2` — also **an ap2**. ⇒ **Nicht ap3-spezifisch, sondern an jedem AP.** +Das deckt sich mit der Ursprungsmessung vom 08-20 (sechs Flaps in 22 Minuten) und mit +`…@wifi2 disconnected, not responding, signal strength −47` — **Abbruch bei ausgezeichnetem Signal**. + +⚠️ **Zwei Symptome, die NICHT gleichgesetzt werden dürfen:** die Band-Oszillation (beide Male +gemessen) und der Abbruch *„not responding"* bei gutem Signal. Christophs Beschreibung passt auf das +**zweite**. Ob dieselbe Ursache dahintersteht, ist **offen**. + +**Zeitachse:** am **2026-08-17** liefen TP28 (CAPsMAN-Cutover) und TP27 (7.24) gleichzeitig. Vorher +waren ap1/ap2 Standalone **ohne Steering und ohne Neighbor-Group**, und `sec-main` fuhr +`ft=yes ft-over-ds=`**`no`**. Beides änderte sich an diesem Tag. Das Beschwerdefenster deckt sich — +**Korrelation, kein Beweis.** + +**Was die MikroTik-Doku dazu hergibt (recherchiert 2026-08-30):** +- `transition-threshold` Default **−80 dBm**, `-time` **10 s**, `-request-period` **30 s**, + `-count` **3**. Unsere Steering-Objekte setzen **nichts** davon. Bei −53…−74 dBm wird das iPhone + also kaum Transition-Kandidat ⇒ **unsolicited BTM scheidet als Treiber weitgehend aus.** +- **WPA3 + 802.11r:** die Doku nennt **nur** „802.11r geht nicht über **OWE**". Eine + WPA3-PSK/FT-Inkompatibilität steht dort **nicht** — sie kursiert in Foren. **Hypothese.** +- **`2g-probe-delay`**: dokumentiertes Band-Steering (2,4-GHz-AP ignoriert die ersten 3 + Probe-Requests je Client pro 60 s). Bei uns **nicht gesetzt**. +- Bei **dynamischen** Neighbor-Groups markiert RouterOS 5 GHz als *preferable*. Wir nutzen die + **statische** Gruppe `bf-main` über beide Bänder; ob die Präferenz dort greift, sagt die Doku + nicht ⇒ **offene Frage**. + +**✅ Erledigt 2026-08-30: die Beweislage ist jetzt dauerhaft.** `wireless`- und `caps`-Logs gehen in +den Kollektor (`10.40.20.18:514`, dieselbe Senke wie TP5), und `/system logging` steht seit heute +unter der **27. ComplianceRule `system-logging`** — vorher war der Block unter keiner Regel und im +Template gar nicht vorhanden. Vorher lief alles nur in einen 75-Zeilen-Ringpuffer. + +**🚫 Kein Funk-Parameter wird geändert, solange keine Daten da sind.** Drei Hebel, **einzeln** und je +als Transaktion (`gc-transaktion.py`), in dieser Reihenfolge: + +| # | Hebel | Begründung der Reihenfolge | Risiko | +|---|---|---|---| +| 1 | `2g-probe-delay=yes` (2,4-GHz-Configs) | MikroTiks *dafür gebautes* Werkzeug; verzögert nur Probe-Antworten und **kann keinen Client stranden lassen** | sehr gering | +| 2 | `ft-over-ds=no` auf `sec-main` | nimmt die Änderung vom 08-17 zurück; FT bleibt über `ft=yes` (over-the-air) erhalten | gering | +| 3 | `signal-range`-Boden per `access-list` (Fund E im Original) | **kann Clients aussperren** — genau das war 08-22 das Gegenargument; nur mit gemessenem Wert | hoch | + +*(Der Defekt war nie gegenstandslos, nur unbelegt: Fund B/C wurden am 08-20 mit ap1 auf `ether15` gemessen — also in genau der Stellung, in der ap1 heute wieder hängt. Er ist platzierungsunabhängig.)* **✅ Erledigte Funde — Ergebnis und Merkregel (Volltext im Archiv):** diff --git a/nautobot/ingest-logging-rule.py b/nautobot/ingest-logging-rule.py new file mode 100644 index 0000000..f1403ba --- /dev/null +++ b/nautobot/ingest-logging-rule.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +""" +ComplianceRule `system-logging` anlegen (idempotent). TP25 Fund E, 2026-08-30. + +Die Luecke, die das schliesst +----------------------------- +`wireless`-Logs liefen bis 2026-08-30 NUR nach `action=memory`. Nur `firewall` ging in +den Kollektor (`tp5syslog` -> 10.40.20.18:514). Ein wiederkehrendes Client-Problem +hinterliess damit KEINE dauerhafte Spur: der Ringpuffer fasste an dem Tag 75 Zeilen und +war von einer Stunde ap3-Arbeit fast ueberschrieben. Christophs seit Wochen gemeldeter +iPhone-Abbruch war rueckwirkend deshalb nicht diagnostizierbar. + +Am 2026-08-30 sind zwei Regeln dazugekommen (`topics=wireless` und `topics=caps`, beide +-> tp5syslog). Ohne ComplianceRule waeren sie ungeschuetzt: sie koennten still +verschwinden, und niemandem fiele es auf -- bei einer Beweissicherung, die wochenlang +laufen soll, ist das der falsche Zustand. Dieselbe blinde Stelle wie bei `/queue` +(TP6) und `/ip firewall filter` (TP26). + +⚠️ RouterOS UND-verknuepft Topics innerhalb EINER Regel (`topics=info,!firewall` heisst +"info UND NICHT firewall"). `topics=wireless,caps` haette deshalb NICHTS gematcht -- +es braucht zwei Regeln. Und der Topic heisst `wireless`, NICHT `wifi`, auch auf dem +neuen wifi-qcom-Stack (am Geraet nachgelesen, nicht geraten). + +Warum ein Praefix reicht +------------------------ +`/system logging action add ...` beginnt ebenfalls mit `/system logging`, ein einziges +Praefix fasst also Regeln UND Actions. Und weil RouterOS Defaults nicht exportiert, +ist der Block auf ap1/ap2/ap3/branch LEER -- Soll und Ist sind dort beide leer, also +compliant. Nur der Core traegt Zeilen. + + python3 nautobot/ingest-logging-rule.py # Dry-Run + python3 nautobot/ingest-logging-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 = "system-logging" +BESCHREIBUNG = ("TP25 Fund E: /system logging unter Drift-Kontrolle. Die wireless-/caps-Regeln " + "in den Kollektor sind Beweissicherung und duerfen nicht still verschwinden.") +MATCH = "/system logging" +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 2b76313..5f63222 100644 --- a/nautobot/mikrotik_routeros.j2 +++ b/nautobot/mikrotik_routeros.j2 @@ -160,6 +160,27 @@ /system ntp client servers add address=at.pool.ntp.org /system ntp server set enabled=yes +# [6a2] LOGGING — Beweissicherung, kein Betriebsdetail. TP25 Fund E, 2026-08-30. +# +# Bis dahin liefen `wireless`-Logs NUR nach `action=memory`; nur `firewall` ging in den +# Kollektor. Der Ringpuffer fasste 75 Zeilen und war von einer Stunde Arbeit fast +# ueberschrieben -- ein seit Wochen gemeldeter iPhone-Abbruch war rueckwirkend deshalb +# nicht diagnostizierbar. Die zwei neuen Zeilen sind die Datengrundlage fuer die +# Entscheidung ueber die Steering-Hebel (2g-probe-delay / ft-over-ds / signal-range). +# +# ⚠️ Der Topic heisst `wireless`, NICHT `wifi` -- auch auf dem neuen wifi-qcom-Stack +# (am Geraet nachgelesen). Ein geratenes `wifi` waere wirkungslos geblieben. +# ⚠️ RouterOS UND-verknuepft Topics innerhalb EINER Regel (`info,!firewall` = info UND +# NICHT firewall). `topics=wireless,caps` haette NICHTS gematcht -> zwei Regeln. +# 📌 Nur der Core traegt hier Zeilen: RouterOS exportiert Defaults nicht, und unter +# CAPsMAN fuehrt ohnehin der MANAGER die Roaming-Events -- die CAPs loggen lokal nichts. +# Auf ap1/ap2/ap3/branch sind Soll und Ist beide leer, also compliant. +/system logging action add name=tp5syslog remote=10.40.20.18 target=remote +/system logging set 0 topics=info,!firewall +/system logging add action=tp5syslog topics=firewall +/system logging add action=tp5syslog comment="TP25 Fund E: WLAN-Roaming/Disconnects dauerhaft in den Kollektor" topics=wireless +/system logging add action=tp5syslog comment="TP25 Fund E: CAP-An-/Abmeldungen dauerhaft in den Kollektor" topics=caps + # [6b] WLAN — CAPsMAN. Seit TP28 (2026-08-17) ist der Core der Manager und ap1/ap2 # sind reine CAPs. Die fuenf wifi-*-ComplianceFeatures gehoeren deshalb HIERHER # und nicht mehr in den AP-Zweig; auf den APs ist die entsprechende Config @@ -247,11 +268,16 @@ # --scope wifi-access-list --change-id TEST-... --expires JJJJ-MM-TT # # 🔴 Hier landet TP25 **Fund E** (`signal-range`-Boden gegen klebende Clients), -# sobald er entschieden ist. Stand 2026-08-22 ist er es NICHT: der Sticky-Client -# vom 08-20 reproduzierte sich beim Rundgang am 08-22 nicht, und in den schwachen -# Raeumen gibt es keinen besseren AP, auf den ein Boden steuern koennte. Ein -# voreilig gesetzter Boden verschlechtert die Terrasse, statt zu helfen. -# Vor dem Eintragen also: Sticky-Fall erneut messen. Siehe teilprojekte.md TP25. +# sobald er entschieden ist. Die alte Auflage lautete "vor dem Eintragen den +# Sticky-Fall erneut messen" -- das ist am 2026-08-30 passiert: dasselbe iPhone +# machte 16 Bandwechsel in 20 Minuten bei -53..-81 dBm, erst an ap3, dann an ap2. +# Fund E ist damit WIEDER OFFEN. +# Eingetragen wird hier trotzdem NICHTS, und zwar bewusst: ein signal-range-Boden +# ist der invasivste der drei Hebel und kann Clients AUSSPERREN -- genau das war +# 08-22 das Gegenargument (in den schwachen Raeumen gibt es keinen besseren AP, +# auf den ein Boden steuern koennte). Erst kommen (1) `2g-probe-delay=yes` und +# (2) `ft-over-ds=no`, jeder einzeln und als Transaktion, mit dem seit 2026-08-30 +# laufenden wireless-Logging als Messgrundlage. Siehe teilprojekte.md TP25 Fund E. # Kanonischer Mgmt-Nummernplan (User-Bestaetigung 2026-08-13, verbindlich): # core .99.1 | branch .99.2 | ap1 .99.11 | ap2 .99.12