Dynamischer DNS Client für IPv6

Dynamisches DNS wird genutzt um den DNS Eintrag für einen Host dessen IP sich regelmäßig und häufig ändert zu aktualisieren. Dies ermöglicht z.B. das Bereitstellen von Diensten hinter einem (V)DSL-Anschluss unter einem gleich bleibenden Namen. Es gibt mehrere Anbieter die einen solchen Dienst anbieten. Ich nutze z.B. SPDyn. Die Aktualisierung des DNS-Eintrags kann durch einen Client wie z.B. ddclient oder auch direkt über eine Fritzbox erfolgen. Für IPv4 Hosts ist das Verfahren einfach, die IP ist immer die des Routers der dann bestimmte Ports an interne Hosts weiterleitet (Portforwarding). Da ich von meinem Provider einen Dual-Stack Anschluss erhalten habe, hatte ich in der Vergangenheit auch einen AAAA-Record für IPv6 bei SPDyn angelegt. Worauf ich allerdings nicht achtete: mein Provider vergibt bei jedem Neuverbinden ein anderes IPv6 Präfix. Dadurch ändert sich also auch jedes mal die IPv6-Adresse meines internen Hosts. Aufmerksam auf diese Problematik wurde ich durch einen Fehler beim Erneuern meines Let’s Encrypt Zertifikats (vgl. Artikel zu acme-tiny).

Wenn ein AAAA-Record vorhanden ist bevorzugt Let’s Encrypt seit kurzem diesen um die Domain zu verifizieren (API Announcements). Da mein vor langer Zeit angelegter AAAA-Record sich mittlerweile (schon oft) geändert hat, schlug das Erneuern des Zertifikats also fehl.

Parsing account key...
Parsing CSR...
Registering account...
Already registered!
Verifying domain.com...
Traceback (most recent call last):
  File "acme_tiny.py", line 199, in 
    main(sys.argv[1:])
  File "acme_tiny.py", line 195, in main
    signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, log=LOGGER, CA=args.ca)
  File "acme_tiny.py", line 150, in get_crt
    domain, challenge_status))
ValueError: domain.com challenge did not pass: {u'status': u'invalid', u'validationRecord': [{u'addressesResolved': [u'217.251.47.207', u'2003:86:2455:9c00:d1:3ff:fe81:bd3f'], u'url': u'http://domain.com/.well-known/acme-challenge/PDUNtwiHq5dncDrvs4V2NE9nSR9vLF2WhnAbX1jQ7f0', u'hostname': u'domain.com', u'addressesTried': [], u'addressUsed': u'2003:86:2455:9c00:d1:3ff:fe81:b46f', u'port': u'80'}], u'keyAuthorization': u'PDUNtwiHq5dncDrvs4V2NE9nSR9vLF2WhnAbX1jQ7f0.YW7Ac9LxjjuWvWzD542ZzSKxcFKDIdehVVzAuYA0vHI', u'uri': u'https://acme-v01.api.letsencrypt.org/acme/challenge/-yjij3RP1r4YC_TkQrUemgjhfWI17pQZSjMZ8kr-Lps/1441804350', u'token': u'PDUNtwiHq5dncDrvs4V2NE9nSR9vLF2WhnAbX1jQ7f0', u'error': {u'status': 400, u'type': u'urn:acme:error:connection', u'detail': u'Fetching http://domain.com/.well-known/acme-challenge/PDUNtwiHq5dncDrvs4V2NE9nSR9vLF2WhnAbX1jQ7f0: Timeout'}, u'type': u'http-01'}

In der Fehlerausgabe ist bei addressUsed zu sehen, dass eine IPv6-Adresse zur Überprüfung verwendet wird. Da die Adresse falsch ist kommt es zu einem Timeout.

Um diesen Fehler zu beheben und in Zukunft auch per IPv6 auf meinen Host zu Hause zugreifen zu können muss nicht nur der A-Record für IPv4 sondern eben auch der AAAA-Record für IPv6 bei einer Änderung aktualisiert werden.

Im Wiki von SPDyn wird beschrieben wie eine Fritzbox konfiguriert werden kann um IPv4 und IPv6 Einträge zu aktualisieren. Allerdings wird dann der AAAA-Record auf die IPv6-Adresse der Fritzbox aktualisiert und nicht auf die Adresse des eigentlichen Ziel-Hosts. Ich verwende die Fritzbox also nur um den A-Record für IPv4 zu aktualisieren. SPDyn ermöglicht das Aktualisieren der DNS-Einträge auch durch den Aufruf einer URL, z.B. mit wget. Für IPv6 habe ich mir deshalb ein kleines Script geschrieben, dass per Cronjob regelmäßig auf dem Host ausgeführt wird, auf den ich per IPv6 zugreifen möchte.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/bin/bash

USER="user"
PASSWORD="password"
HOSTNAME="subdomain.spdns.org"
CACHE_FILE="/tmp/update_ipv6_cache"

while getopts ":d" opt; do
    case $opt in
        d)    DEBUG=1;;
    esac
done


IPV6=`ip addr show eth0 | grep 'scope global dynamic' | grep -Po 'inet6 \K[0-9a-fA-F:]+' | grep -v fd00`
URL="https://update.spdyn.de/nic/update?hostname=$HOSTNAME&myip=$IPV6"
if [ -f $CACHE_FILE ]; then
    IPV6_CACHE=`cat $CACHE_FILE`
else
    IPV6_CACHE=""
fi

if [[ $IPV6 == $IPV6_CACHE ]]; then
    if [[ $DEBUG -eq 1 ]]; then
       echo "IP not changed"
    fi
    exit
else
    echo $IPV6 > $CACHE_FILE
    if [[ $DEBUG -eq 1 ]]; then
        echo wget -q -O - "$URL" --http-user=$USER --http-password=$PASSWORD
        wget -q -O - "$URL" --http-user=$USER --http-password=$PASSWORD
    else
        wget -q -O - "$URL" --http-user=$USER --http-password=$PASSWORD >/dev/null 2>&1
    fi
fi

Durch Variablen werden die notwendigen Daten für den URL-Aufruf (z.B. User und Passwort) definiert. Im ersten Schritt wird die aktuelle IPv6-Adresse ermittelt (Zeile 15). In meinem Fall wird aus allen IPv6 Adressen für das Device eth0 die Adresse herausgefiltert die “global dynamisch” und keine lokale Adresse (fd00) ist. Unter Umständen muss das Ermitteln der richtigen IP angepasst werden. Danach wird die ermittelte IPv6-Adresse mit einem evtl. vorhandenen gecachten Wert verglichen (Zeile 23). Ergibt der Vergleich, dass sich die Adresse nicht geändert hat wird das Script beendet. Es ist keine Aktualisierung des DNS-Eintrags notwendig. Sind die beiden Adressen nicht identisch bzw. keine gecachte Adresse vorhanden wird die URL zum Aktualisieren des DNS-Eintrags mit wget aufgerufen (Zeile 34). Beim Start des Script mit dem Parameter -d (für Debug) wird zusätzlich der wget-Aufruf (Zeile 31) bzw. eine Info wenn sich die Adresse nicht geändert hat (Zeile 25) ausgegeben.

Per Cronjob wird das Script bei mir alle 15 Minuten gestartet und aktualisiert somit bei Bedarf den DNS-Eintrag bei SPDyn. Um mit dem Host kommunizieren zu können sind zusätzlich noch Portfreigaben für IPv6 in der Fritzbox notwendig. Nach dieser Änderung kann auf den Host nun per IPv6 zugegriffen werden und auch die Erneuerung des Let’s Encrypt Zertifikats funktioniert wieder.