self hosted dynamic dns using bind9 (named) and openresty (nginx)

First step is to configure your domain properly. My domain provider (gandi) let me edit my zone file, so I put in it the following content:

@ 10800 IN A 1.2.3.4 ns 10800 IN A 1.2.3.4 * 10800 IN CNAME example.org. *.ns 10800 IN NS ns

The first line tells to get the ip of my vps server when asking the dns record for example.org; the second line do the same for ns.example.org; the third line says to get *.example.org pointing to the same address of the bare domain (CNAME is like an alias); the last line is the most interesting: the NS record says that *.ns.example.org will be resolved by the dns server ns.example.org. 1.2.3.4 is a placeholder for the ip address of my vps server. Depending on the provider you may have to wait minutes to hours before the edit will propagate.

The next step is to install and start bind9 on your server; refer to your OS manual to get the proper software running. Once I got it up and running on port 53 I opened the /etc/named.conf file and added my zone section:

zone "example.org" { type master; file "dnsdb.example.org"; allow-update { localhost; }; };

With this configuration I told bind9 to get the zone information in /var/named/dnsdb.example.org and that dns update requests are accepted only if originating on localhost.

Next step is to create the zone file; I copied the empty.zone file and edited the relevant bits, and added a record for ns.

$ORIGIN example.org. $TTL 1h @ 1D IN SOA example.org. root.example.org. ( 2016120401 ; serial 1H ; refresh 15M ; retry 1W ; expiry 1D ) ; min ttl 1D IN NS ns.example.org. ns.example.org. 1D IN A 127.0.0.1

After restarting named I tested the configuration inserting a subdomain using nsupdate.

$ cat > add_loopback.txt <<EOF server localhost zone example.org update add loopback.ns.example.org 3600 A 127.0.0.1 show send EOF $ nsupdate add_loopback.txt Outgoing update query: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 0 ;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0 ;; ZONE SECTION: ;example.org IN SOA ;;UPDATE SECTION: loopback.ns.example.org. 3600 IN A 127.0.0.1 $

I checked that the result was public:

$ ping loopback.ns.example.org PING loopback.ns.example.org (127.0.0.1) 56(84) bytes of data. 64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=1 ttl=64 time=0.026 ms ^C --- loopback.ns.example.org ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.026/0.026/0.026/0.000 ms

Next step was getting openresty up and running on my server; again, refer to your OS manuals and documentation.

I edited the openresty's nginx.conf. In my distribution it is /opt/openresty/nginx/conf/nginx.conf.

[...] server { listen 80; server_name ns.example.org; location /nsadmin { content_by_lua_file conf/nsadmin.lua; } } [...]

I created the following nsadmin.lua script in the same directory of nginx.conf file:

local key = ngx.var.arg_key local domain = ngx.var.arg_domain local ip = ngx.var.arg_ip fd = io.open("/opt/openresty/nginx/conf/credentials.txt") for line in fd:lines() do local lkey, ldomain = line:match("^([^:]*):([^:]*)$") if key == lkey and domain == ldomain then if ip == nil then break end ngx.log(ngx.DEBUG, "updating: k=" .. key .. " d=" .. domain .. " i=" .. ip) local cmd = 'server localhost

' cmd = cmd .. 'zone example.org

' cmd = cmd .. 'update delete ' .. domain .. '.ns.example.org A

' cmd = cmd .. 'update add ' .. domain .. '.ns.example.org 3600 A ' .. ip .. '

' cmd = cmd .. 'show

' cmd = cmd .. 'send

' ngx.say('nsupdate command:

' .. cmd) p = io.popen("echo '" .. cmd .. "' | nsupdate 2>&1") for pline in p:lines() do ngx.say(pline .. "

") end p.close() return end end fd:close() ngx.say("unauthorized")

The credentials.txt file content is simple: one key:domain pair per line:

mysecretpassword:mysubdomainname

And finally after restarting openresty the server configuration is complete.

On the client side I put in a cron job the update of the relevant ip address (in my case it was the internal network address of a wifi device, in a network where I can't get access to the dhcp administration):

curl -v 'http://ns.example.org/nsadmin?key=mysecretpassword&domain=mysubdomainname&ip='$(ifdata -pa wlan0)

A handy windows .bat to update the domain ip address:

@if (@This==@IsBatch) @then wscript //E:JScript "%~dpnx0" exit /b @end var http = WScript.CreateObject('Msxml2.XMLHTTP.6.0'); http.open('GET','http://icanhazip.com', false); http.setRequestHeader('User-Agent','curl'); http.send(); var externalIP = http.responseText; var updateUrl = "http://ns.example.org/nsadmin?key=mysecretpassword&domain=mysubdomainname&ip=" + externalIP; http = WScript.CreateObject('Msxml2.XMLHTTP.6.0'); http.open('GET', updateUrl, false); http.setRequestHeader('User-Agent','curl'); http.send(); WScript.Echo(http.responseText); WScript.Quit(0);