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:
@@ -1,15 +1,28 @@
|
|||||||
#!/bin/sh
|
#!/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
|
if ! command -v curl >/dev/null 2>&1; then
|
||||||
|
echo "[DNS-MONITOR] 'curl' not found. Installing curl..."
|
||||||
apk add --no-cache curl
|
apk add --no-cache curl
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[DNS-MONITOR] Service started. Monitoring $SUBDOMAINS every 5 minutes..."
|
echo "[DNS-MONITOR] Service started. Monitoring $SUBDOMAINS every 5 minutes..."
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
# Fetch Public IP
|
# 1. Fetch Public IP (via external service)
|
||||||
PUBLIC_IP=$(curl -s -m 10 https://api.ipify.org)
|
# We use -4 to force IPv4
|
||||||
|
PUBLIC_IP=$(curl -s -4 -m 10 https://api.ipify.org)
|
||||||
|
|
||||||
if [ -z "$PUBLIC_IP" ]; then
|
if [ -z "$PUBLIC_IP" ]; then
|
||||||
PUBLIC_IP="Error: Could not fetch IP"
|
PUBLIC_IP="Error: Could not fetch IP"
|
||||||
@@ -23,20 +36,32 @@ while true; do
|
|||||||
else
|
else
|
||||||
FULL_DOMAIN="${FIRST_SUB}.duckdns.org"
|
FULL_DOMAIN="${FIRST_SUB}.duckdns.org"
|
||||||
|
|
||||||
# Resolve IP using ping
|
# 2. Resolve Global IP (Google DNS @8.8.8.8) to check propagation
|
||||||
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)
|
# We look for the A record (IPv4)
|
||||||
|
if command -v dig >/dev/null 2>&1; then
|
||||||
if [ -z "$DOMAIN_IP" ]; then
|
GLOBAL_IP=$(dig @8.8.8.8 +short A "$FULL_DOMAIN" | head -n 1)
|
||||||
DOMAIN_IP="Error: Could not resolve"
|
LOCAL_IP=$(dig +short A "$FULL_DOMAIN" | head -n 1)
|
||||||
|
else
|
||||||
|
GLOBAL_IP="Error: dig missing"
|
||||||
|
LOCAL_IP="Error: dig missing"
|
||||||
fi
|
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')
|
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
if [ "$PUBLIC_IP" == "$DOMAIN_IP" ]; then
|
# Logic to determine status
|
||||||
echo "[$TIMESTAMP] [DNS-MONITOR] OK | Domain: $FULL_DOMAIN ($DOMAIN_IP) == Public: $PUBLIC_IP"
|
STATUS="OK"
|
||||||
else
|
if [ "$PUBLIC_IP" != "$GLOBAL_IP" ]; then
|
||||||
echo "[$TIMESTAMP] [DNS-MONITOR] ALERT | Domain: $FULL_DOMAIN ($DOMAIN_IP) != Public: $PUBLIC_IP"
|
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
|
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
|
fi
|
||||||
|
|
||||||
# Check every 5 minutes
|
# Check every 5 minutes
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ services:
|
|||||||
- PGID=1000 # Group ID (anpassen falls nötig)
|
- PGID=1000 # Group ID (anpassen falls nötig)
|
||||||
- TZ=Europe/Berlin
|
- TZ=Europe/Berlin
|
||||||
- SUBDOMAINS=floke,floke-ai,floke-gitea,floke-ha,floke-n8n
|
- 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
|
restart: unless-stopped
|
||||||
|
|
||||||
# --- DNS MONITOR (Sidecar) ---
|
# --- DNS MONITOR (Sidecar) ---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# DuckDNS & DNS Monitor Setup
|
# DuckDNS & DNS Monitor Setup
|
||||||
|
|
||||||
**Erstellt am:** 05.01.2026
|
**Erstellt am:** 05.01.2026
|
||||||
|
**Zuletzt aktualisiert:** 06.01.2026
|
||||||
**Status:** Aktiv
|
**Status:** Aktiv
|
||||||
**Architektur:** Sidecar-Pattern
|
**Architektur:** Sidecar-Pattern
|
||||||
|
|
||||||
@@ -9,11 +10,13 @@ Dieses Dokument beschreibt die Docker-basierte Lösung zur DynDNS-Aktualisierung
|
|||||||
## Problembeschreibung
|
## 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.
|
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
|
## Lösung
|
||||||
Wir haben eine robuste **Zwei-Container-Lösung** (Sidecar-Pattern) implementiert:
|
Wir haben eine robuste **Zwei-Container-Lösung** (Sidecar-Pattern) implementiert:
|
||||||
|
|
||||||
1. **Updater (`duckdns`):** Aktualisiert zuverlässig die IP bei DuckDNS (Image: `linuxserver/duckdns`).
|
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`)
|
## Konfiguration (`docker-compose.yml`)
|
||||||
|
|
||||||
@@ -34,7 +37,10 @@ Dieser Container sorgt dafür, dass DuckDNS immer die aktuelle IP kennt.
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2. Der Monitor (Sidecar)
|
### 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
|
```yaml
|
||||||
dns-monitor:
|
dns-monitor:
|
||||||
@@ -49,7 +55,7 @@ Dieser Container prüft alle 5 Minuten die DNS-Auflösung und schreibt das Ergeb
|
|||||||
restart: unless-stopped
|
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
|
## Logs & Monitoring
|
||||||
|
|
||||||
@@ -57,7 +63,6 @@ Dieser Container prüft alle 5 Minuten die DNS-Auflösung und schreibt das Ergeb
|
|||||||
```bash
|
```bash
|
||||||
docker logs duckdns
|
docker logs duckdns
|
||||||
```
|
```
|
||||||
*Erwartet:* `Your IP was updated at ...` oder `IP(s) unchanged`.
|
|
||||||
|
|
||||||
### DNS-Konsistenz prüfen (WICHTIG!)
|
### DNS-Konsistenz prüfen (WICHTIG!)
|
||||||
Hier sehen wir, ob die Welt (bzw. der Container) die Domain korrekt auflöst.
|
Hier sehen wir, ob die Welt (bzw. der Container) die Domain korrekt auflöst.
|
||||||
@@ -68,15 +73,32 @@ docker logs dns-monitor
|
|||||||
|
|
||||||
**Szenario OK:**
|
**Szenario OK:**
|
||||||
```text
|
```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
|
```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
|
## Dateien
|
||||||
- `/app/docker-compose.yml`: Definition der Services.
|
- `/app/docker-compose.yml`: Definition der Services.
|
||||||
- `/app/dns-monitor/monitor.sh`: Das Shell-Skript für den Monitor-Container.
|
- `/app/dns-monitor/monitor.sh`: Das Shell-Skript für den Monitor-Container.
|
||||||
|
|||||||
Reference in New Issue
Block a user