Strukturelle Hygiene (Handoff 2026-09-22): Pflichtlektüre auf 3 Pointer, Kritische Gotchas auf Kern-Cluster komprimiert, ausführliche Diagnose-Blöcke (SSDP-Relay, DNS-Hang, networking.service, NTP, HA-http, Nautobot-healthcheck …) nach docs/gotchas.md verschoben. Tote ALLNET/Omada-Einträge entfernt, "korrigiert"-Notizen bereinigt (nur die korrigierte Angabe behalten). Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
10 KiB
Gotchas — ausführliche Diagnose-Anleitungen (Detail-Doku)
Diese Datei trägt die ausführlichen Diagnose-Blöcke, die in CLAUDE.md nur noch als 1-Zeilen-Merkregel
mit Zeiger stehen. Volltext hier, damit der Kern-Index schlank bleibt. Alle Blöcke sind aktiv
(querschnittliche Fallen), keine Historie.
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.
- 🔴
--ssdpUnicastAddrgehö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 inmulticast-relay.pyZ. 630 die Quelle jedes SSDP-Pakets an239.255.255.250:1900auf<addr>:1901um (auch NOTIFY); beim Senden greift dann Z. 691if srcAddr == ssdpUnicastAddr and not onNetwork(srcAddr, tx['addr'], tx['netmask']): continue—10.40.10.21liegt 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->MAINist 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) gegengrep -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-relaymitlesen. Und am Client nicht auf Port 1900 messen: Sonos-App und Spotify halten dort je einSO_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. - 🔴 Zweite, unabhängige Ursache desselben Falls (08-25): die Sonos-App suchte gar nicht. Nach der
Relay-Reparatur lag weiter nichts an — der Mitschnitt auf der VLAN40-Leitung
(
pct exec 108 -- tcpdump -i eth1 -n -s0 -A "udp and host <client>") zeigte: jedes SSDP-Paket des MacBooks kam von Chrome (ST: urn:dial-multiscreen-org:…dial:1,USER-AGENT: Chromium), kein einziges von Sonos. Die App hatte Sockets offen (*:1900,*:6969,TCP *:3400) und redete mit der Cloud, legte aber nichts auf die Leitung. Ursache war das Local-Network-Gate von macOS: es stand auf „an" und griff trotzdem nicht. Fix = Schalter unter Datenschutz & Sicherheit → Lokales Netzwerk aus, App beenden, wieder an, App starten (hart:sudo tccutil reset LocalNetwork com.sonos.macController2). ⇒ Merkregel: „die App sucht hunderte Male" ist erst dann ein Befund, wenn der Mitschnitt sagt, WER sucht. Im Handoff vom Vormittag waren die gezählten 291 Suchen Chrome + eigene Proben; die Zahl hat drei Stunden in die falsche Richtung gezeigt. - 🖥️ Das MacBook ist dual-homed in VLAN10:
en5= 10.40.10.183 (USB-Dock, Default-Route) unden0= 10.40.10.82 (WLAN). Die früher als „zweiter Mac" gelesene.183ist dasselbe Gerät — beim Messen immer beide Beine prüfen.
Konstanter 10-Sekunden-Hänger = DNS-Timeout, kein Lastproblem
(Fund 2026-09-01, TP16.3-Randbefund.) gitea hing bei ~10 % der Anfragen exakt 10,1 s — quer über
Web-UI, /api/healthz und den Git-Pfad, also kein Rendering. Ursache: MariaDB (LXC 302) hatte
skip-name-resolve auskommentiert und machte bei jeder neuen Verbindung einen Reverse-Lookup,
während ihr /etc/resolv.conf noch auf den toten 10.40.41.254 zeigte (glibc: 5 s × 2 Versuche = 10 s).
Pool-Verbindungen waren schnell ⇒ es wirkte sporadisch.
🔑 Drei Merkregeln daraus:
- (a) immer gegen eine zweite Sache messen, die denselben Weg nimmt —
vw/hassüber dieselbe Strecke hatten 0 von 30 Hängern, das hat CF, Tunnel, Firewall und Caddy in einem Schritt entlastet. - (b) wer an einer DB hängt, sagt die Verbindungstabelle (
ss -tn | grep 3306), nicht die Vermutung — hier hingen Vaultwarden, Home Assistant und gitea an derselben MariaDB, und die Annahme „Vaultwarden nutzt SQLite" war schlicht falsch. - (c) ein toter Resolver ist selten allein — der Sweep über alle LXCs fand fünf Container mit
10.40.41.254. Sweep:for c in $(pct list|awk 'NR>1{print $1}'); do pct exec $c -- grep ^nameserver /etc/resolv.conf; done
networking.service failed bei funktionierendem Netz = tote auto-Stanzas
(Ursache gefunden + behoben 2026-09-01.) In vier LXCs standen in /etc/network/interfaces noch
Einträge aus dem alten VLAN1-Schema (eth0 → 10.40.41.x) und aus früheren Versuchen (veth16,
veth24, Tippfehler eth01) — auf Interfaces, die es im Container gar nicht gibt. ifup -a scheitert
daran, das echte veth20 kommt trotzdem hoch. Fix: nur die Stanzas entfernen, deren Interface in
ip -br link fehlt (lo und die echten behalten), Backup daneben.
🔴 Abnahme ist ein echter Reboot, nicht systemctl is-active — der Servicestatus sagt nichts
darüber, ob beim nächsten Start etwas hochkommt.
networking.service failed im LXC, ohne dass das Netz fehlt
networking.service kann in einem LXC dauerhaft failed sein, ohne dass das Netz fehlt — und dann
startet alles mit After=network-online.target nach einem Reboot nicht (Fund 2026-09-01: 101,
102, 106, 107; influxdb2 kam nach dem Neustart nicht hoch, Unit enabled, kein Startversuch
im Journal). Fällt nur auf, wenn man neu startet — die Container liefen 13+ Tage.
Vor jedem geplanten LXC-Neustart systemctl --failed lesen und danach die Ports gegen vorher
vergleichen, sonst verliert man still einen Dienst.
Messwerkzeug, das die falsche Frage beantwortet
(zweimal in einer Nacht, 2026-09-01.)
- (a)
nc -zlieferte falsch-negative „Port zu" für neun Dienste gleichzeitig — inklusive des SSH-Ports, über den die Session gerade arbeitete;curlbekam von demselben Host HTTP 200 in 24 ms. Bei „alles gleichzeitig kaputt" zuerst das eigene Werkzeug verdächtigen, dann das Netz: ein Python-Socket odercurlentscheidet in Sekunden. - (b) Ein Firewall-Zähler taugt nicht als Wegbeweis, wenn der Client die Verbindung offenhält
(
cloudflared): er zählt nur NEUE Verbindungen und steht still, obwohl alles läuft — richtig ist die Verbindungstabelle (/ip firewall connection).
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.
Zeitquelle ist der Core (NTP)
/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=nound 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/=yesnach 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.
Home Assistant: der http:-Block in configuration.yaml ist WIRKUNGSLOS
(Fund 2026-08-26, TP31.) HA hat die http-Config nach .storage/http migriert
("yaml_migration_done": true); seither wird die YAML kommentarlos ignoriert — selbst ein ungueltiger
Schluessel darin erzeugt keinen Config-Fehler.
🔴 Die Logzeile fuehrt aktiv in die Irre: Received X-Forwarded-For header from an untrusted proxy <ip> faellt laut Quelltext nur bei aktivem use_x_forwarded_for — man haelt die Option fuer wirksam
und die Liste fuer falsch und editiert die falsche Datei. Richtig ist der WS-Zweischritt
http/config/configure (setzt pending, startet HA neu) → binnen 5 Minuten
http/config/promote, sonst Auto-Revert auf stable.
Merkregel zur Eingrenzung: ein Testskript in <config>/python_scripts/ + python_script.reload —
taucht der Service auf, liest HA das Verzeichnis, und die Frage ist nicht mehr welche Datei, sondern
warum ein gelesener Block nicht wirkt.
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).