30 python lines Dynamic DNS

Here in Spain, I chose Movistar as my Internet provider, I must say I’m pretty happy with it, symmetric 300Mbps fiber optics and good service. The only annoying aspect is that they do not provide static IP for free, something I was used to and was very convenient.

In order to reach my network from places where I can’t connect to my VPN, I wrote a very simple Dynamic DNS system using dnspython, and it turned out to be fairly easy.

This is the code running on my public server:

$ cat ddns.py
from flask import Flask

import dns.update
import dns.query
import dns.tsigkeyring
import dns.resolver

app = Flask(__name__)

@app.route('/query/<t>/<fqdn>')
def query(t, fqdn):
    resolver = dns.resolver.Resolver(configure=False)
    resolver.nameservers = ['127.0.0.1']
    answer = dns.resolver.query(fqdn, t)
    if t == 'a':
        return '{0}\n'.format(answer.rrset[0].address)

@app.route("/update/<domain>/<host>/<ip>", methods=['POST'])
def update(domain, host, ip):

    keyring = dns.tsigkeyring.from_text({
        'rndc-key' : 'myRNDCkey=='
    })

    update = dns.update.Update('{0}.'.format(domain), keyring=keyring)
    update.replace(host, 300, 'A', ip)
    response = dns.query.tcp(update, '127.0.0.1', timeout=10)

    return "update with {0}\n".format(ip)

if __name__ == "__main__":
    app.run()

This code is served by gunicorn:

$ gunicorn ddns:app 

Behind an nginx reverse proxy:

location /dyndns/ {
    auth_basic "booh.";
    auth_basic_user_file /etc/nginx/htpasswd;
    proxy_pass http://private-server:8000/;
}

On the client side (home), I wrote this very simple shell script:

#!/bin/sh

PATH=${PATH}:/sbin:/usr/sbin:/bin:/usr/bin:/usr/pkg/bin

name="mybox"
domain="mydomain"
fqdn="${name}.${domain}"
auth="user:mymagicpassword"

# here retrieve your actual public IP from a website like httpbin.org
curip=$(curl -s -o- http://some.website.like.httpbin.org/ip)
# fetch recorded IP address
homeip=$(curl -u ${auth} -s -o- https://my.public.server/dyndns/query/a/${fqdn})

if [ "${curip}" != "${homeip}" ]; then
        warnmsg="/!\\ home IP changed to ${curip} /!\\"

        echo "${warnmsg}"|mail -s "${warnmsg}" me@mydomain.net

        curl -u ${auth} \
                -X POST https://my-public.server/dyndns/update/${domain}/${name}/${curip}
fi

Which is cron’ed to run every 5 minutes.

And here I am with my own cheap Dynamic DNS system.