From e416b795e8da30ea5ba76b9059ddc6874690ac1f Mon Sep 17 00:00:00 2001 From: Christoph Emsenhuber Date: Tue, 25 Aug 2026 18:28:53 +0200 Subject: [PATCH] TP7.1 nachgebessert: --ssdpUnicastAddr raus, SSDP-Hinweg repariert Die macOS-Sonos-App fand ihre Player nicht. Ursache war nicht die Firewall (MEDIA->MAIN ist offen) und nicht der vermutete Race um den globalen "last SSDP search source"-Slot, sondern die Flagge --ssdpUnicastAddr selbst: multicast-relay.py Z. 630 schreibt die Quelle JEDES SSDP-Pakets an 239.255.255.250:1900 auf :1901 um (auch NOTIFY), Z. 691 verwirft das Paket dann beim Senden auf eth1, weil 10.40.10.21 nicht im VLAN40-Netz liegt. Der Hinweg lief damit nie. Messung vorher: 0 Pakete via eth1 gegen 308 via eth0 in 20 min, 0x "Received SSDP Unicast" im gesamten Journal seit 2026-08-18. Nachher: M-SEARCH quellerhaltend in VLAN40, HTTP/1.1 200 OK von 10.40.40.193/.195 an beiden MacBook-Beinen, Drop-Log in:vlan40 out:vlan10 = 0. - mcast-relay/multicast-relay.service: reparierte Unit jetzt im Repo (ausgerollt auf LXC 108, Backup /root/multicast-relay.service.bak-20260825) - CLAUDE.md: Gotcha korrigiert, Richtungsbilanz als Erstmessung, Hinweis auf die SO_REUSEPORT-Falle auf Port 1900 und das dual-homed MacBook (.183/.82) - docs/teilprojekte.md: Ist-Stand nachgezogen, TP33 (Fernzugriff) als Stub - docs/teilprojekte-archiv.md: TP7.1-Aussage "--ssdpUnicastAddr ist noetig" widerrufen Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 6 ++++- docs/teilprojekte-archiv.md | 8 ++++-- docs/teilprojekte.md | 30 ++++++++++++++++++--- mcast-relay/multicast-relay.service | 41 +++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 mcast-relay/multicast-relay.service diff --git a/CLAUDE.md b/CLAUDE.md index 043aac4..feef66e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -125,7 +125,11 @@ GW je VLAN = `.1` (crs326-core L3) · Mgmt VLAN99: core `.99.1`, branch `.99.2`, - 🔥 **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.** **MAIN→INFRA/MEDIA/MGMT/IOT stehen dauerhaft offen** (IOT seit TP5 E5, 2026-08-19 — Drucker + Shellys); **IOT→MAIN bleibt bewusst zu**, dort sitzt der Wert der Segmentierung. Ad-hoc-Zugriff aus MAIN in ein **noch geschlossenes** VLAN (UNTRUSTED, GUEST) läuft über die selbstverfallende Liste: `/ip firewall address-list add list=jit-admin address= timeout=1h`. *(Die früher hier genannten Beispiele „Scannen, IoT anlernen" brauchen jit-admin seit E5 nicht mehr.)* - 🖨️ **Merkregel aus dem E5-Drop-Log:** der Drucker fällt mit **udp/161 (SNMP)** auf, nicht mit 631 — macOS fragt zuerst den SNMP-Status ab und bricht ab, bevor es zu IPP kommt. **Eine Whitelist auf 631 hätte nichts repariert.** Bei „Gerät antwortet nicht" also das Log lesen, statt den erwarteten Port zu raten. - ⏰ **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. +- 📡 **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). Unit im Repo: **`mcast-relay/multicast-relay.service`**. **Zwei Beine sind zwingend** — SSDP ist link-lokal mit TTL 1, ein Relay muss Mitglied beider Broadcast-Domänen sein; Routing hilft nicht. + - 🔴 **`--ssdpUnicastAddr` gehört in ein zweibeiniges Relay NICHT — die Flagge hat den Hinweg 7 Wochen lang blockiert** (drin 2026-08-18, ausgebaut **2026-08-25**). Sie schreibt in `multicast-relay.py` **Z. 630** die Quelle *jedes* SSDP-Pakets an `239.255.255.250:1900` auf `:1901` um (**auch NOTIFY**); beim Senden greift dann **Z. 691** `if srcAddr == ssdpUnicastAddr and not onNetwork(srcAddr, tx['addr'], tx['netmask']): continue` — `10.40.10.21` liegt nicht im VLAN40-Netz, also **verwarf der Relay die M-SEARCH selbst, bevor sie die Player erreichte**. Der Rückweg gehört stattdessen **geroutet**: `forward TP5: MEDIA->MAIN` ist offen, die Player antworten unicast an den echten Sucher. (Upstream nennt den Rückweg selbst `# FIXME: record more than one?` — es gibt nur **einen globalen** Merkslot.) + - 🔎 **Symptom-Merkregel:** *App sucht hunderte Male, sieht NOTIFYs, baut aber nie eine Verbindung auf* = **Relay, nicht Netz**. Diagnose in einer Zeile — **Richtungsbilanz** im Journal: `grep -c "via eth1"` (VLAN10→VLAN40) gegen `grep -c "via eth0"`. **0 zu n heißt: der Hinweg fehlt.** + - ⚠️ **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. **Und am Client nicht auf Port 1900 messen:** Sonos-App *und* Spotify halten dort je ein `SO_REUSEPORT`-Socket, die Unicast-Antwort bekommt nur **eines** davon. Richtig ist eine Probe mit **ephemerem Quellport** (`bind((ifaddr, 0))`), genau wie ein echter Client sucht. + - 🖥️ **Das MacBook ist dual-homed in VLAN10:** `en5` = **10.40.10.183** (USB-Dock, Default-Route) und `en0` = **10.40.10.82** (WLAN). Die früher als „zweiter Mac" gelesene `.183` ist **dasselbe Gerät** — beim Messen immer beide Beine prüfen. - 🐳 **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.) diff --git a/docs/teilprojekte-archiv.md b/docs/teilprojekte-archiv.md index 197e6d8..d667c84 100644 --- a/docs/teilprojekte-archiv.md +++ b/docs/teilprojekte-archiv.md @@ -618,8 +618,12 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". erweitert (`routeros/tp7.1-core-ether13-trunk.rsc`). Software: `alsmith/multicast-relay` als systemd-Unit `multicast-relay.service`. - **`--noMDNS` ist Pflicht:** mDNS reflektiert bereits der Core (TP7). Zweimal zu reflektieren - erzeugt Duplikate. **`--ssdpUnicastAddr 10.40.10.21`** ist ebenfalls noetig — die Sonos - antworten auf einen M-SEARCH per **Unicast**, und diese Antworten muessen zurueckgereicht werden. + erzeugt Duplikate. ~~**`--ssdpUnicastAddr 10.40.10.21`** ist ebenfalls noetig — die Sonos + antworten auf einen M-SEARCH per **Unicast**, und diese Antworten muessen zurueckgereicht werden.~~ + 🔴 **WIDERRUFEN 2026-08-25:** die Flagge war der Fehler — sie schreibt in `multicast-relay.py` Z. 630 + die Quelle jedes SSDP-Pakets um, worauf Z. 691 die M-SEARCH beim Senden auf `eth1` **selbst verwirft** + (`10.40.10.21` ist nicht im VLAN40-Netz). Der Hinweg lief damit **nie**; ausgebaut, Rückweg wird + geroutet (`TP5: MEDIA->MAIN`). Stand + Merkregeln: Ist-Stand in `teilprojekte.md` und Gotcha in `CLAUDE.md`. - 🔑 **Warum zwei Beine zwingend sind** (Christophs Frage „vlan40 sollte keinen relay benoetigen?!?"): SSDP ist **link-lokales Multicast mit TTL 1**. Der M-SEARCH des MacBooks verlaesst VLAN10 nie, die `ssdp:alive`-NOTIFYs der Sonos verlassen VLAN40 nie. Ein Relay kann nur kopieren, was es selbst diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index 77b5ecb..023cfed 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -50,9 +50,14 @@ ab jetzt bei jeder Änderung mitgezogen, nicht nur die TP-Abschnitte.)* (an core `ether15`), ap2 **`10.40.99.12`** (an branch `ether24`), beide **reine CAPs** — `cap enabled=yes`, `discovery-interfaces=vlan99`. Die APs halten **keine eigene WLAN-Config** mehr; Kanäle, SSIDs, Security und Steering kommen vom Manager. 802.11r/k/v aktiv, `ft-over-ds=yes`. -- **SSDP-Relay** (TP7.1 ✅ 08-18): LXC **108 `mcast-relay`** auf pve, zweibeinig `10.40.10.21` (VLAN10) + - `10.40.40.21` (VLAN40) — dafür trägt `core ether13` beide VLANs. Die macOS-Sonos-App findet damit ihr - System; mDNS macht weiterhin der Core. +- **SSDP-Relay** (TP7.1, **nachgebessert 08-25**): LXC **108 `mcast-relay`** auf pve, zweibeinig + `10.40.10.21` (VLAN10) + `10.40.40.21` (VLAN40) — dafür trägt `core ether13` beide VLANs; mDNS macht + weiterhin der Core. **Läuft seit 08-25 ohne `--ssdpUnicastAddr`** — die Flagge verwarf die M-SEARCH + der App auf dem Weg nach VLAN40 (Messung: **0 Pakete `via eth1` gegen 308 `via eth0`** in 20 min, + **0×** `Received SSDP Unicast` im ganzen Journal seit 08-18). Der Rückweg läuft jetzt **nativ geroutet** + über die längst offene Regel `TP5: MEDIA->MAIN`; nachgemessen kommen `HTTP/1.1 200 OK` von + `10.40.40.193/.195` an beiden MacBook-Beinen an. Unit liegt im Repo: **`mcast-relay/multicast-relay.service`**, + Details als Gotcha in `CLAUDE.md`. - **Ausgebaut / gelöscht:** ALLNET-`sw1` und Omada-`sw2` sind **physisch weg** (2026-08-08 bzw. 2026-07-23, 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, @@ -1521,6 +1526,25 @@ kann in Ruhe nachziehen. - **Abh.:** keine. **Blockiert nichts**, aber TP31 vergibt bereits danach. +### TP33 — Sonos aus der Ferne bedienen (VPN/WARP) ▸ 💡 **IDEE, bewusst zurückgestellt** (angelegt 2026-08-25, User-Entscheid „später entscheiden") + +- **Auslöser:** beim Sonos-Fall vom 08-25 kam die Frage auf, ob die Player auch **von unterwegs** + auffindbar sein sollen. Antwort für jetzt: **nein, später entscheiden** — hier nur festgehalten, + damit die Begründung nicht verloren geht. +- 🔑 **Der Kernsatz:** **SSDP und mDNS queren einen Tunnel nicht von selbst.** Beides ist link-lokal + (TTL 1 bzw. Multicast auf `224.0.0.251`), ein WireGuard-/WARP-Tunnel ist ein Punkt-zu-Punkt-Interface + ohne Broadcast-Domäne. Es bräuchte also **dieselbe Relay-Bauart wie TP7.1, nur ein zweites Mal** — + ein Bein im Tunnel, eines in VLAN40 — plus die passende `forward`-Regel (TP5 ist deny-by-default). +- ⚖️ **Warum zurückgestellt:** echter Zusatzaufwand für einen **Randfall**. Steuern aus der Ferne geht + heute schon über die **Sonos-Cloud-App** und über **HA** (`cf-mesh`/CF-Tunnel), beides ohne Discovery. + Fehlt nur die Desktop-App von außen — dafür lohnt der zweite Relay-Strang derzeit nicht. +- ⚠️ **Nicht mit dem Gegenteil verwechseln:** **mit aktivem WARP findet die App die Player auch lokal + nicht** — erwartbares Verhalten, kein Fehler (`ref_warp_local_icmp_trap`, Gotcha in `CLAUDE.md`). + Wer den Fernzugriff baut, muss genau diesen Fall mit auflösen. +- **Abh.:** TP7.1 (Relay-Muster) ✅, TP16.1 (`cf-mesh`) ✅. **Blockiert nichts.** + +--- + --- ## Abgeschlossene Teilprojekte ▸ [`teilprojekte-archiv.md`](teilprojekte-archiv.md) diff --git a/mcast-relay/multicast-relay.service b/mcast-relay/multicast-relay.service new file mode 100644 index 0000000..4b2481f --- /dev/null +++ b/mcast-relay/multicast-relay.service @@ -0,0 +1,41 @@ +# TP7.1 — SSDP-Relay VLAN10 <-> VLAN40 (macOS-Sonos-App) +# +# Laeuft auf LXC 108 "mcast-relay" (pve), zweibeinig: +# eth0 = 10.40.10.21 (VLAN10/MAIN) eth1 = 10.40.40.21 (VLAN40/MEDIA) +# Ablage im Repo: mcast-relay/multicast-relay.service +# Ziel auf dem Container: /etc/systemd/system/multicast-relay.service +# Ausrollen: scp … root@10.40.20.100:/tmp/ && pct push 108 /tmp/multicast-relay.service \ +# /etc/systemd/system/multicast-relay.service && pct exec 108 -- systemctl daemon-reload \ +# && pct exec 108 -- systemctl restart multicast-relay +# +# --noMDNS ist WICHTIG: mDNS reflektiert bereits der Core (TP7, mdns-repeat-ifaces). +# Zweimal zu reflektieren erzeugt Duplikate und Schleifen. +# +# 🔴 KEIN --ssdpUnicastAddr (2026-08-25 ausgebaut, war seit 2026-08-18 drin und hat den +# Hinweg blockiert). Die Flagge schreibt in multicast-relay.py Z. 630 die Quelle JEDES +# SSDP-Pakets an 239.255.255.250:1900 auf die angegebene Adresse:1901 um — auch NOTIFY. +# Beim Senden greift dann Z. 691: +# if srcAddr == ssdpUnicastAddr and not onNetwork(srcAddr, tx['addr'], tx['netmask']): continue +# 10.40.10.21 liegt nicht im VLAN40-Netz ⇒ die M-SEARCH der App wurde verworfen, BEVOR sie +# die Player erreichte (Messung: 0 Pakete via eth1 / 308 via eth0 in 20 min, 0 "Received SSDP +# Unicast" im ganzen Journal). Zusaetzlich ist der Rueckweg im Upstream nur EIN globaler +# Merkslot (Z. 623 "FIXME: record more than one?"). +# Ohne die Flagge relayt das Werkzeug quellerhaltend; die Player antworten unicast an den +# echten Sucher und der Core routet das nativ zurueck — forward-Regel "TP5: MEDIA->MAIN" +# ist offen (in=vlan40 out=vlan10, ohne Portfilter). +[Unit] +Description=TP7.1 SSDP-Relay vlan10 <-> vlan40 (macOS-Sonos-App) +Documentation=docs/teilprojekte.md TP7.1 +After=network-online.target +Wants=network-online.target + +[Service] +ExecStart=/usr/bin/python3 /opt/multicast-relay/multicast-relay.py \ + --interfaces eth0 eth1 \ + --noMDNS \ + --foreground --verbose +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target