Files
bf-infra/CLAUDE.md
Christoph Emsenhuber 1ba82608f2 CAPsMAN: slaves-datapath fehlte, IOT+MEDIA lagen 14h
Seit dem CAPsMAN-Cutover (2026-08-17) war `/interface wifi cap
slaves-datapath` auf ap1 und ap2 leer. Das ist das einzige Feld, das die
vom Manager erzeugten Slave-VAPs lokal in die Bridge haengt: der Core
schickt `datapath.bridge=bridge` zwar mit, der CAP uebernimmt davon aber
nur die vlan-id. Ergebnis: -iot, -media, -untrusted und -guest hingen in
gar keiner Bridge — Clients assoziierten sauber und bekamen nie DHCP.
`dirtyvader-main` lief weiter, weil wifi1/wifi2 statische Bridge-Ports
sind; genau das hat den Ausfall getarnt.

Fix live auf beiden APs: dp-caps-slaves (bridge=bridge, bewusst ohne
vlan-id) + slaves-datapath darauf. Die VAPs stehen seither als dynamische
Bridge-Ports mit pvid 30/40/50/90 in der Bridge.

Nicht die TP5-Haertung: torch auf vlan30 am Core leer UND kein einziger
DHCP-Eintrag im Drop-Log — es wurde nichts verworfen, es kam nichts an.

tp28-ap-wifi-reste-abraeumen.rsc hat den Ausfall nicht ausgeloest, aber
zementiert: mit den geloeschten datapath-Objekten gab es lokal nichts
mehr, worauf slaves-datapath haette zeigen koennen.

Die TP28-Abnahme "ohne jeden Aussetzer" ist korrigiert — sie zaehlte
Registrations, also genau die Metrik, die hier luegt. Abnahmekriterium
fuer WLAN-Umbauten ist ab jetzt eine frische DHCP-Lease pro SSID.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 09:13:26 +02:00

18 KiB
Raw Blame History

BF-Netzwerk — Projekt-Kontext (auto-read)

Standort BF: VLAN-segmentiertes Heimnetz auf MikroTik/RouterOS. Ziel: CAKE/QoS gegen Bufferbloat, DNS/DHCP-Ablösung, WLAN via CAPsMAN auf dem Core (TP28, 2026-08-17 — der Standalone-Entscheid von 2026-07-22 ist revidiert), Firewall Least-Privilege (TP5 deny-by-default scharf seit 2026-08-18), Multi-Site VPN.

Für jede TP-Session: zuerst docs/teilprojekte.md lesen — enthält kanonischen Stand, Scope, Abhängigkeiten und Reihenfolge aller Teilprojekte. Kein anderer Kontext nötig.


VLAN-Modell (kanonisch)

VLAN Name Subnetz Zweck
10 MAIN 10.40.10.0/24 Trusted (Handy/Laptop)
20 INFRA 10.40.20.0/24 NAS/VMs/Docker/HA
30 IOT 10.40.30.0/24 Smart-Home
40 MEDIA 10.40.40.0/24 Sonos/AppleTV/TV/Cast
50 UNTRUSTED 10.40.50.0/24 Konsole/Low-Trust
90 GUEST 10.40.90.0/24 nur Internet
99 MGMT 10.40.99.0/24 Switch/AP/Hypervisor-Mgmt
100 WAN (Arris-Transfer) zum Arris-Kabelmodem (Liwest, Public-IP)

GW je VLAN = .1 (crs326-core L3) · Mgmt VLAN99: core .99.1, branch .99.2, ap1 .99.11, ap2 .99.12


Hardware & Zugang

Gerät IP Zugang
ALLNET Core-Switch (sw1) — ❌ außer Betrieb (2026-08-08) — durch crs326-core (L3-Router + Core-Switch, Technik UG) ersetzt; physisch abgelöst. (Telnet-Runner switch/ = nur noch historisch.)
Omada Access-Switch (sw2) — ❌ abgebaut 2026-07-23 (TP15 Branch-Tausch) — durch crs326-branch in Technik OG ersetzt, physisch entfernt
crs326-branch (CRS326, Technik OG) 10.40.99.2 (VLAN99) · Bootstrap Rev4 2026-08-08 RouterOS 7.24 · admin/~/.secrets (CRS326_BRANCH_*) · REST http://10.40.99.2/rest · MikroMCP crs326-branch ✅ live (2026-08-16 verifiziert) · TP15 ✅: ersetzt sw2 (L2-Switch), volle Portbelegung (ether1 Uplink-Trunk, 2/8 NAS, 3 Drucker, 4 Dock, 5/6 Sonos, 7 macmini, 24 ap2) · sw-id RGZJ-UK0T, MAC 04:F4:1C:B4:15:58
crs326-core (CRS326, Technik UG) 10.40.99.1 (VLAN99-SVI = L3-GW; same-subnet-Fallback 10.40.10.1) · Identity crs326-core RouterOS 7.24 · admin/~/.secrets (CRS326_CORE_*) · MikroMCP routerId crs326-core ✅ live (2026-08-16 verifiziert) · TP15/TP21.1 ✅ 2026-08-08: PRODUKTIVER L3-Router (ersetzt ap1) — Routing/DHCP (7 Server + 54 Leases)/DNS (DoH zum CF-Gateway mit verify-doh-cert=yes, TP23 Stufe 1 ✅ 2026-08-18; servers=1.1.1.1 nur noch Bootstrap) · NTP-Server für den Standort/NAT/WG-Hub (fm+dw live); WAN vlan100=Liwest 90.146.33.177 · sw-id 4W04-URHT, MAC 04:F4:1C:B4:17:76
hAP ax² #1 (ap1) 10.40.99.11 (VLAN99) · Identity ap1 RouterOS 7.24 · admin/~/.secrets (ROUTER_AP1_*) · REST http://10.40.99.11/rest · MikroMCP ap1 ✅ live · TP28 ✅ 2026-08-17: reiner CAP (Manager = crs326-core, cap enabled=yes, discovery-interfaces=vlan99) — Uplink ether1 (PoE-Port, poe-out=off) an crs326-core ether15; SSIDs/Kanäle kommen vom Manager, ch36/ch1 · MAC D0:EA:11:4D:40:E0 · Config-Referenz routeros/.secrets/ap1-prov-base.rsc
hAP ax² #2 (ap2) 10.40.99.12 (VLAN99) · Identity ap2 RouterOS 7.24 · admin/~/.secrets (ROUTER_AP2_*) · REST http://10.40.99.12/rest · MikroMCP ap2 · TP28 ✅ 2026-08-17: reiner CAP — Uplink ether1 (PoE) an crs326-branch ether24; ch44/ch11 (TP25: ch149 war unter ETSI leistungsbegrenzt) · MAC D0:EA:11:4D:40:3A · Config routeros/.secrets/ap2-prov-base.rsc
🔑 Einheitliches Admin-PW (2026-08-08) alle 4 MikroTik (core/branch/ap1/ap2) user admin, gleiches PW (~/.secrets/credentials.env → ROUTER_*_PASS/CRS326_*_PASS; „Überlebensversicherung", User-Vorgabe). Werks-Defaults ap1/ap2 damit obsolet.
NAS (nas-bf, DS923) 10.40.20.10 SSH adm.christoph@10.40.20.10 (kein PW nötig) · Docker (Portainer endpointId=14)
platform-pg (LXC 105, pve) 10.40.20.20 SSH via pve (pct exec 105 --) · TP20 ✅ 2026-07-16: PG16, DB nautobot, Creds postgres/.secrets
Nautobot (Portainer-Stack, NAS) 10.40.20.30 Portainer ep14, Stack Id 203 · TP21 ✅ 2026-07-16: ghcr.io/nautobot/nautobot:3.1.7, DB=platform-pg, Creds in docs/nas-vlan20/stacks/210-nautobot.yml (gitignored) · TP22 ✅ 2026-08-13: Golden-Config-App + Device-Adoption DURCH — alle 4 MikroTik adoptiert, Deploy-Pipeline scharf (Image 3.1.7-gc9), 68/68 Compliance-Checks grün / 0 Drifts (2026-08-16)
Omada-Controller — ❌ entfernt 2026-07-16 (User hat den Container gelöscht — overkill für 1 Switch auf der Abschussliste, s. TP2.1/TP15)
omada-mcp — ❌ entfernt 2026-07-16 — mit dem Controller gelöscht. ✅ Aufräumen erledigt/gegenstandslos (geprüft 2026-08-16): nicht mehr in mcpServers registriert (nur noch als „disabled" in ein paar Projekt-Scopes = harmloser Merkposten), und kein omada-Hostname mehr im CF-Ingress
pve-mcp 10.40.20.173 StreamableHTTP http://10.40.20.173:3000/ (Root-Pfad, NICHT /sse) · NAS-Stack ep14 (=nas-bf .20.10, NICHT LXC .126!) id 202 (vlan20-only, TP14.1 2026-07-10) · mcp-pve v0.6.1 (Upgrade 2026-07-10: Single-Session-Bug 0.4.8 „Server already initialized" gefixt, TP14.2a) · ~/.claude.json ✓
Proxmox (pve) 10.40.20.100 ssh root@10.40.20.100 (SSH-Key ✓) · TP14 2026-07-13: Host-Mgmt VLAN1→VLAN20 (vmbr0.20 + Default-GW-Flip); .41.100 bleibt interim ohne Route
Docker-LXC (auf pve) 10.40.20.126 Portainer CE 2.40.0 (https://10.40.20.126:9443) · TP14.2 2026-07-10: LXC 400 net0 tag=20/.20.126 (iface heißt jetzt veth20, NICHT eth0); Docker-Netz vlan20=ipvlan parent veth20; adguard_bf1 .20.16, adguard_fm2 .20.17
Mac mini 10.40.20.167 (VLAN20) ssh macmini · ⚠️ die früher hier stehende 10.40.41.167 war stale (VLAN1) — live korrigiert 2026-08-16 (ARP reachable auf vlan20)
Arris-Kabelmodem (Bridge-Mode, keine Mgmt-IP im BF-LAN) Liwest-Edge · speist crs326-core vlan100 (VLAN100-Transfer, WAN-IP 90.146.33.177) — seit Core-Cutover 2026-08-08, davor via sw1 gi16 → ap1 · FritzBox 6660 seit 2026-07-01 komplett ausgebaut (Liwest-Tausch, kein Zurück); Telefonie nicht weitergeführt

Tooling

  • MikroTik (core/branch/ap1/ap2): MikroMCP-Server live → mcp__mikromcp__*-Tools (routerId crs326-core/crs326-branch/ap1/ap2). Reads immer safe; Writes mit dryRun:true previewen, dann apply_plan. Passwort setzen via manage_user set-password (/user set* ist per Deny-Policy blockiert). bridge/vlan/set nur auf [find … and !dynamic].
  • ALLNET-Switch (sw1): ❌ Gerät ausgebaut 2026-08-08 → der Telnet-Runner unter switch/ ist Archiv, nicht mehr ausführbar (kein Endpunkt). Siehe switch/README.md. Nicht mehr als Werkzeug anbieten.
  • Omada-Switch (sw2): ❌ abgebaut 2026-07-23 (TP15, durch crs326-branch ersetzt). Controller + omada-mcp komplett gelöscht — Omada spielt keine Rolle mehr, nie wieder aufgreifen (User-Entscheid 2026-07-22). Aufräum-Reste ✅ geprüft 2026-08-16 (s. Tabelle oben).
  • Nautobot Golden Config (= der einzige legitime Config-Weg für Netzgeräte): nautobot/gc-configplan.py (Token = NAUTOBOT_SUPERUSER_API_TOKEN aus docs/nas-vlan20/stacks/210-nautobot.yml, gitignored). --job <name> legt manuelle Pläne an (Dry-Run default), --deploy-ids … deployt gestaffelt, --refresh-compliance fährt Backup → Intended → Compliance in der einzig richtigen Reihenfolge. ⚠️ POST /api/plugins/golden-config/config-plan/ ist 405 — das ViewSet ist read-only. ConfigPlans entstehen nur über den Job Generate Config Plans (plan_type=manual + commands-TextVar).
  • Docker-Management: Portainer API https://10.40.20.126:9443, Key in mcp/portainer/.env. Ablösung durch Komodo (native MCP) geplant.

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.
  • hAP ax² hat 128 MB Flash, kein USB → Container NICHT auf dem Router; Container laufen auf NAS/pve.
  • VLAN20-Docker-Hosts: Host-DNS nach dem Umzug prüfen (2026-07-10, TP14.2a): NAS + pve-LXC zeigten /etc/resolv.conf noch auf totes .41.254 → docker pull ghcr scheitert (EAI_AGAIN, aber Egress ok!). Fix = .20.1: LXC pct set 400 --nameserver 10.40.20.1; Synology-Falle: DSM-„Allgemein"-DNS ≠ was dockerd liest — Interface zieht DNS per DHCP → Fix an der Netzwerkschnittstelle „DNS manuell". Laufende Container frieren den 127.0.0.11-Upstream zum Erstellzeitpunkt ein (Recreate nötig). Merkregel: docker-pull-hängt + dns.lookup EAI_AGAIN aber TCP zu ghcr-IP:443 ok = toter Resolver, nicht Egress.
  • 📜 ALLNET VLAN-DB (TP1) — historisch. Die VLAN-Semantik gilt weiter (40=MEDIA, 50=UNTRUSTED), die Portangaben (GE1/GE2/GE8) beziehen sich auf ein ausgebautes Gerät.
  • ap2 hängt an crs326-branch ether24 (Trunk, native VLAN1 + tagged 10-99); branch-Uplink ether1 → crs326-core ether8 (korrigiert 2026-08-16 — die frühere Angabe „→ sw1 GE8" stammt aus der Zeit vor dem Core-Tausch; MAC-hart belegt in TP22 Schritt 5).
  • Mesh = NEIN (beide APs haben wired Trunk-Uplink → Loop-Risiko).
  • Beide APs = CAPs, Manager ist crs326-core (TP28 ✅ 2026-08-17). Der Entscheid „kein CAPsMAN" von 2026-07-22 ist revidiert: er galt der Konstellation ap1=Manager+AP, in der ein Manager seine eigenen Radios nicht provisionieren kann — mit dem Core als Manager sind beide APs symmetrisch. Der Core braucht dafür kein wifi-qcom, capsman steckt im Basispaket.
    • 🔴 Messfalle: unter CAPsMAN führt der MANAGER die Tabellen. /interface/wifi/registration-table und die wireless-Logs sind auf ap1/ap2 leer, obwohl das WLAN läuft. Immer am Manager zählen.
    • 🔴 Die CAPs finden den Manager per Layer-2-Discovery (discovery-interfaces=vlan99, current-caps-man-address ist eine MAC, keine IP). Deshalb müssen MNDP (udp/5678) und CAPWAP (udp/5246-5247) in der input-Kette des Core erlaubt bleiben — sonst kommt das WLAN nach dem nächsten AP-Reboot nicht zurück, während laufende Sessions unauffällig weiterlaufen (Fund 2026-08-18, TP5 Etappe 4).
    • Local Forwarding ist keine Einstellung mehr: im neuen /interface/wifi gibt es kein zentrales Forwarding, datapath existiert dort nicht.
    • 🔴🔴 /interface wifi cap slaves-datapath MUSS auf beiden APs gesetzt sein (dp-caps-slaves, ein lokales datapath-Objekt mit bridge=bridge und ohne vlan-id). Es ist das Einzige, was die vom Manager erzeugten Slave-VAPs lokal in die Bridge haengt: der Manager schickt datapath.bridge=bridge mit, der CAP uebernimmt davon nur die vlan-id. Fehlt es, assoziieren Clients auf -iot/-media/-untrusted/-guest sauber und bekommen nie DHCP, waehrend dirtyvader-main weiterlaeuft — dessen Master-Radios wifi1/wifi2 sind statische Bridge-Ports. Genau so lagen IOT+MEDIA am 2026-08-18 rund 14 Stunden (Ursache: CAPsMAN-Cutover 08-17, nicht die TP5-Haertung). Abnahme fuer jeden WLAN-Umbau ist eine frische DHCP-Lease pro SSID — NICHT die Registration-Table, die zeigte waehrend des Ausfalls 25 zufriedene IOT-Clients.
    • 🔎 Merkregel „connect ok, kein DHCP": zuerst torch auf dem betroffenen VLAN am Core UND den TP5-Drop-Log lesen. Sind beide leer, wird nichts verworfen — es kommt nichts an, und die Firewall ist entlastet. Dann liegt es an L2 (Bridge-Mitgliedschaft/Tagging), nicht am Regelwerk.
  • mgmt-VLAN-Wechsel am Switch = Lockout-Risiko: VLAN muss existieren + Port muss es tragen, in einem Schritt.
  • 🔥 Die Firewall ist seit 2026-08-18 deny-by-default — in BEIDEN Ketten. Ein neuer Dienst über eine VLAN-Grenze braucht ab jetzt eine explizite Regel, sonst funktioniert er einfach nicht. Diagnosewerkzeug ist die Log-Regel direkt vor dem Drop (TP5-WOULD-DROP / TP5-INPUT-WOULD-DROP), die weiterläuft und in den Kollektor 10.40.20.18:514 schreibt (Dateien auf dem Docker-LXC unter /opt/tp5-syslog/log/). Erst dort nachsehen, bevor irgendwo anders gesucht wird. Ad-hoc-Zugriff aus MAIN in ein anderes VLAN (Scannen, IoT anlernen) läuft über die selbstverfallende Liste: /ip firewall address-list add list=jit-admin address=<deine-IP> timeout=1h.
  • ⏰ Zeitquelle ist der Core (/system ntp server, seit 2026-08-18), ausgeliefert per DHCP-Option 42 (ntp-server= in jedem der 7 Netze) und drift-überwacht (ComplianceRule system-ntp). Vorher lief nirgends eine Synchronisation: die anderen drei Geräte zeigten auf 10.40.99.1, wo kein Server lief, und der Core selbst hatte gar keinen NTP-Client — ap2 war dadurch 17 Stunden versetzt. 🔴 Zwei Fallen dabei: (a) ein NTP-Server mit use-local-clock=no und unsynchronisiertem Client liefert Stratum 16, das jeder Client verwirft — Server einschalten allein reicht nicht; (b) wird der Server eingeschaltet, bevor sein Client synchron ist, hält er diesen Zustand fest → enabled=no/=yes nach der Synchronisation. Merkregel: bei widersprüchlichen Zeitstempeln zwischen Geräten zuerst /system ntp client get status lesen, und zum Eingrenzen einen Client testweise auf eine öffentliche Quelle zeigen lassen.
  • 📡 SSDP-Relay für die macOS-Sonos-App: LXC 108 mcast-relay auf pve, zweibeinig 10.40.10.21 (VLAN10) + 10.40.40.21 (VLAN40), multicast-relay mit --noMDNS (mDNS macht der Core) und --ssdpUnicastAddr. Zwei Beine sind zwingend — SSDP ist link-lokal mit TTL 1, ein Relay muss Mitglied beider Broadcast-Domänen sein; Routing hilft nicht. ⚠️ Aus dem Relay heraus lässt sich der Relay nicht testen (Loop-Schutz ignoriert eigene Quelladressen) — am echten Client testen oder journalctl -u multicast-relay mitlesen.
  • 🐳 Nautobot auf der DS923 braucht einen abgeschalteten Image-Healthcheck. Der eingebackene Check ruft alle 10 s nautobot-server health_check = einen vollen Django-Start; unter Last dauert der >2 min, die Aufrufe stapeln sich unbegrenzt → Speicher alle → fork() mit ENOMEM → DSM räumt den ganzen Stack ab (am 2026-08-17 zweimal passiert, Load bis 73 bei 4 Kernen). Im Stack stehen deshalb healthcheck: disable: true auf worker+scheduler, --concurrency=2 und mem_limit. cpus: geht auf Synology nicht (Kernel ohne CFS-Quota, der Stack-Deploy schlägt sonst fehl).
  • 📜 Omada ES208G = Easy Managed — gegenstandslos (Gerät abgebaut 2026-07-23, Controller gelöscht).
  • L3-Router = crs326-core (TP21.1 ✅ 2026-08-08, ersetzt ap1). Bridge vlan-filtering=yes; VLAN-SVIs .1 je VLAN mit DHCP (7 Server / 54 Leases); DNS über DoH zum CF-Gateway, verify-doh-cert=yes (TP23 Stufe 1 ✅ 2026-08-18). Die alte Notiz „core hat kein CA" war zu pessimistisch: es braucht kein Bundle, sondern genau einen Root (GTS Root R4, ~1 KB) — die Kette ist Leaf → GTS WE1 → R4. Beide Firewall-Ketten enden seit 2026-08-18 mit drop (TP5 E3/E4); TP10-seal-lan ist disabled, Mgmt-Input kommt nur noch aus VLAN10, von HA (8728), von Nautobot (22) und über cf-mesh. ap1/ap2 sind NUR noch APs — kein DHCP/DNS/NAT/Routing/WG dort. (Die frühere „TP4: ap1 ist L3-Router"-Zeile ist überholt.)
  • ✅✅ Edge-Cutover (2026-07-01; Router-Umzug 2026-08-08): FritzBox ausgebaut, Arris im Bridge-Mode. Liwest hat die FB6660 dauerhaft gegen ein Arris getauscht (physisch weg). WAN terminiert auf crs326-core vlan100 (VLAN100-Transfer), core zieht die öffentliche Liwest-IP 90.146.33.177 → kein Double-NAT. core = Public-Edge + WG-Hub (fm/dw live), NICHT mehr ap1. Telefonie nicht weitergeführt. Runbook routeros/wan-cutover-runbook.md (ap1-Teile historisch).
  • hAP device-mode=home (ap1/ap2): scheduler + fetch blockiert (not allowed by device-mode) → AP-Provisionierung idempotent ohne Scheduler. [/ping] gibt nil in 7.23. REST je AP http://10.40.99.11|12/rest (Creds = ROUTER_AP{1,2} in ~/.claude.json). Der L3-Router crs326-core ist KEIN device-mode=home → dort gehen scheduler/fetch (z.B. DDNS-Updater).
  • Erstzugriff Werks-hAP am CRS-Fabric: Uplink ether1 = Werks-WAN → 192.168.88.1 (LAN) darüber NICHT erreichbar. Weg: Gerät auf LAN-Port (ether2) patchen ODER Core-Temp-Pfad 192.168.88.2/24 auf core-bridge + srcnat masq 192.168.88.0/24, dann scp+/import (danach Temp-Pfad + stale ARP .88.1 entfernen). Winbox-Neighbors zeigt die APs NICHT (MNDP pro-VLAN, Mgmt auf vlan99) — kein L2-Fehler.
  • P750W (Brother, VLAN30) läuft auf dirtyvader-iot (User-bestätigt 2026-08-08) — kein Access-List-Pin nötig; die alte „nur am co-located L3-GW-AP erreichbar"-Logik ist mit L3-GW auf dem Core hinfällig. Coverage-Kanten/Terrasse grenzwertig (−76..−89 dBm); TX am AT-Max (18/15 dBm); echter Fix = Platzierung. SSoT docs/wifi-p750w-coverage-and-map.md. ⚠️ Power-Save ist KEIN Thema dieses Netzes — die früher hier stehende Merkregel „DHCP-geht-aber-Ping-nicht = ARP/Power-Save" war eine Fehldiagnose (2026-08-13 korrigiert: der Drucker war schlicht ausgeschaltet, das Ping-Symptom war WARP, s.u.). Nicht wieder einführen.
  • Ping auf JEDE lokale IP tot, aber Web/App/Print (TCP) geht überall = WARP-Client lokal versehentlich noch aktiv (2026-08-13 live beobachtet, BF vor Ort): cloudflared/WARP ist QUIC-Proxy, kann kein ICMP, reicht TCP aber durch — auch wenn man physisch im BF-LAN steht, nimmt der Client bei aktivem WARP trotzdem den Tunnel-Umweg. Fix: WARP trennen, kein Router-Eingriff nötig. Vor jeder Firewall-/Mangle-/VLAN-Suche zuerst prüfen, wenn das Symptom „Ping auf ALLES tot, TCP geht" lautet — das ist ein Client-, kein Netzproblem. Strukturell schließt TP16.1 (natives WireGuard-CF-Mesh auf crs326-core statt cloudflared-QUIC) genau diese ICMP-Lücke. Memory ref_warp_local_icmp_trap.