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 <addr>: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 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 18:28:53 +02:00
parent 810d4c4ed6
commit e416b795e8
4 changed files with 79 additions and 6 deletions

View File

@@ -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=<deine-IP> 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 `<addr>: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.)

View File

@@ -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

View File

@@ -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)

View File

@@ -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