Fix(DuckDNS): Resolve zombie updater conflict via token rotation.

- Updated 'docker-compose.yml' with new DuckDNS token to lock out rogue updaters (FritzBox/HA).
- Enhanced 'dns-monitor/monitor.sh' to detect 'IP Flapping' (Zombie Updaters) by comparing Public vs. Global vs. Local IPs.
- Updated documentation 'duckdns_setup.md' with troubleshooting steps for IP flapping and zombie updaters.
This commit is contained in:
2026-01-06 11:43:31 +00:00
parent b2175b574f
commit 912e820a8b
3 changed files with 69 additions and 22 deletions

View File

@@ -1,15 +1,28 @@
#!/bin/sh
# Install curl and ping if not present (Alpine)
# Ensure dependencies are installed
# We check for 'dig' specifically. If missing, we try to install bind-tools.
if ! command -v dig >/dev/null 2>&1; then
echo "[DNS-MONITOR] 'dig' not found. Installing bind-tools..."
if apk add --no-cache bind-tools; then
echo "[DNS-MONITOR] bind-tools installed successfully."
else
echo "[DNS-MONITOR] ERROR: Failed to install bind-tools. Check internet connection or repo mirrors."
fi
fi
# Ensure curl is installed (though it seems to be present based on logs)
if ! command -v curl >/dev/null 2>&1; then
echo "[DNS-MONITOR] 'curl' not found. Installing curl..."
apk add --no-cache curl
fi
echo "[DNS-MONITOR] Service started. Monitoring $SUBDOMAINS every 5 minutes..."
while true; do
# Fetch Public IP
PUBLIC_IP=$(curl -s -m 10 https://api.ipify.org)
# 1. Fetch Public IP (via external service)
# We use -4 to force IPv4
PUBLIC_IP=$(curl -s -4 -m 10 https://api.ipify.org)
if [ -z "$PUBLIC_IP" ]; then
PUBLIC_IP="Error: Could not fetch IP"
@@ -23,20 +36,32 @@ while true; do
else
FULL_DOMAIN="${FIRST_SUB}.duckdns.org"
# Resolve IP using ping
DOMAIN_IP=$(ping -c 1 -W 5 "$FULL_DOMAIN" 2>/dev/null | head -n 1 | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n 1)
if [ -z "$DOMAIN_IP" ]; then
DOMAIN_IP="Error: Could not resolve"
# 2. Resolve Global IP (Google DNS @8.8.8.8) to check propagation
# We look for the A record (IPv4)
if command -v dig >/dev/null 2>&1; then
GLOBAL_IP=$(dig @8.8.8.8 +short A "$FULL_DOMAIN" | head -n 1)
LOCAL_IP=$(dig +short A "$FULL_DOMAIN" | head -n 1)
else
GLOBAL_IP="Error: dig missing"
LOCAL_IP="Error: dig missing"
fi
if [ -z "$GLOBAL_IP" ]; then GLOBAL_IP="Unresolved"; fi
if [ -z "$LOCAL_IP" ]; then LOCAL_IP="Unresolved"; fi
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
if [ "$PUBLIC_IP" == "$DOMAIN_IP" ]; then
echo "[$TIMESTAMP] [DNS-MONITOR] OK | Domain: $FULL_DOMAIN ($DOMAIN_IP) == Public: $PUBLIC_IP"
else
echo "[$TIMESTAMP] [DNS-MONITOR] ALERT | Domain: $FULL_DOMAIN ($DOMAIN_IP) != Public: $PUBLIC_IP"
# Logic to determine status
STATUS="OK"
if [ "$PUBLIC_IP" != "$GLOBAL_IP" ]; then
STATUS="ALERT"
elif [ "$PUBLIC_IP" != "$LOCAL_IP" ]; then
# If Global is correct but Local is wrong, it's a local caching issue, still an alert but specific
STATUS="CACHE_ALERT"
fi
# Log format: Time | Status | Public vs Global vs Local
echo "[$TIMESTAMP] [DNS-MONITOR] $STATUS | Public: $PUBLIC_IP | Global(8.8.8.8): $GLOBAL_IP | Local: $LOCAL_IP"
fi
# Check every 5 minutes

View File

@@ -117,7 +117,7 @@ services:
- PGID=1000 # Group ID (anpassen falls nötig)
- TZ=Europe/Berlin
- SUBDOMAINS=floke,floke-ai,floke-gitea,floke-ha,floke-n8n
- TOKEN=af8f6d8b-5a83-4251-9c15-d3b3e82eeef1
- TOKEN=2e073b27-971e-4847-988c-73ad23e648d4
restart: unless-stopped
# --- DNS MONITOR (Sidecar) ---

View File

@@ -1,6 +1,7 @@
# DuckDNS & DNS Monitor Setup
**Erstellt am:** 05.01.2026
**Zuletzt aktualisiert:** 06.01.2026
**Status:** Aktiv
**Architektur:** Sidecar-Pattern
@@ -9,11 +10,13 @@ Dieses Dokument beschreibt die Docker-basierte Lösung zur DynDNS-Aktualisierung
## Problembeschreibung
Das ursprüngliche Synology-Skript im Aufgabenplaner führte zu unzuverlässigen DNS-Updates. Oft war die IP bei DuckDNS zwar aktuell, aber die Dienste waren von außen nicht erreichbar. Erst ein manuelles Ändern der IP auf einen falschen Wert und zurück (Toggle) löste das Problem. Dies deutete auf DNS-Propagation- oder Caching-Probleme hin.
Zusätzlich wurden "Zombie-Updater" (alte Konfigurationen in FritzBox oder Home Assistant) identifiziert, die die IP immer wieder auf veraltete Werte zurücksetzten ("IP Flapping").
## Lösung
Wir haben eine robuste **Zwei-Container-Lösung** (Sidecar-Pattern) implementiert:
1. **Updater (`duckdns`):** Aktualisiert zuverlässig die IP bei DuckDNS (Image: `linuxserver/duckdns`).
2. **Monitor (`dns-monitor`):** Überwacht unabhängig, ob die Domain tatsächlich auf die aktuelle öffentliche IP auflöst.
2. **Monitor (`dns-monitor`):** Überwacht unabhängig und differenziert die DNS-Auflösung.
## Konfiguration (`docker-compose.yml`)
@@ -34,7 +37,10 @@ Dieser Container sorgt dafür, dass DuckDNS immer die aktuelle IP kennt.
```
### 2. Der Monitor (Sidecar)
Dieser Container prüft alle 5 Minuten die DNS-Auflösung und schreibt das Ergebnis ins Log.
Dieser Container prüft alle 5 Minuten die DNS-Auflösung und schreibt das Ergebnis ins Log. Er vergleicht:
1. **Public IP:** Die tatsächliche externe IP (via ipify).
2. **Global DNS:** Die IP laut Google DNS (8.8.8.8) -> Prüft Internet-Propagation.
3. **Local DNS:** Die IP laut Container-Resolver -> Prüft lokales Caching.
```yaml
dns-monitor:
@@ -49,7 +55,7 @@ Dieser Container prüft alle 5 Minuten die DNS-Auflösung und schreibt das Ergeb
restart: unless-stopped
```
**Wichtig:** Das Skript `monitor.sh` liegt im Host-Verzeichnis `/app/dns-monitor/`.
**Wichtig:** Das Skript `monitor.sh` liegt im Host-Verzeichnis `/app/dns-monitor/` und installiert zur Laufzeit fehlende Pakete (`bind-tools`, `curl`).
## Logs & Monitoring
@@ -57,7 +63,6 @@ Dieser Container prüft alle 5 Minuten die DNS-Auflösung und schreibt das Ergeb
```bash
docker logs duckdns
```
*Erwartet:* `Your IP was updated at ...` oder `IP(s) unchanged`.
### DNS-Konsistenz prüfen (WICHTIG!)
Hier sehen wir, ob die Welt (bzw. der Container) die Domain korrekt auflöst.
@@ -68,14 +73,31 @@ docker logs dns-monitor
**Szenario OK:**
```text
[2026-01-05 21:26:26] [DNS-MONITOR] OK | Domain: floke.duckdns.org (87.x.x.x) == Public: 87.x.x.x
[TIMESTAMP] [DNS-MONITOR] OK | Public: 87.x.x.x | Global(8.8.8.8): 87.x.x.x | Local: 87.x.x.x
```
Alles synchron.
**Szenario FEHLER (Alarm):**
**Szenario ALERT (Zombie-Updater / Propagation):**
```text
[2026-01-05 21:30:00] [DNS-MONITOR] ALERT | Domain: floke.duckdns.org (Error) != Public: 87.x.x.x
[TIMESTAMP] [DNS-MONITOR] ALERT | Public: 87.x.x.x | Global(8.8.8.8): 87.y.y.y | Local: 87.y.y.y
```
Dies deutet darauf hin, dass DuckDNS zwar vielleicht aktualisiert wurde, aber die DNS-Server die Änderung noch nicht propagiert haben.
Dies deutet darauf hin, dass entweder die Propagation noch läuft oder ein anderes Gerät (FritzBox, HA) die IP überschreibt.
**Szenario CACHE_ALERT (Lokales Problem):**
```text
[TIMESTAMP] [DNS-MONITOR] CACHE_ALERT | Public: 87.x.x.x | Global(8.8.8.8): 87.x.x.x | Local: 87.y.y.y
```
Das Internet kennt die neue IP schon, aber lokal wird noch die alte gecacht.
## Troubleshooting: "IP Flapping" (Zombie-Updater)
Wenn die IP im Monitor ständig zwischen der aktuellen (`...252`) und einer alten (`...49`) springt, gibt es einen zweiten Updater im Netzwerk (z.B. Router, Home Assistant, alte Skripte).
**Lösung:**
1. Login bei [duckdns.org](https://www.duckdns.org).
2. **Token rotieren** (Button "update token").
3. Neuen Token **nur** in der `docker-compose.yml` eintragen.
4. `docker-compose up -d duckdns` ausführen.
5. Damit werden alle alten Updater ausgesperrt.
## Dateien
- `/app/docker-compose.yml`: Definition der Services.