Running Your Own Dynamic DNS Server

June 21, 2012
Home

I recently moved my web server from an internet connection with a static IP address to an internet connection with a dynamic IP address, which presents some issues with DNS. I looked at some dynamic DNS services such as Dyn and no-ip (which I’ve used before and was happy with), but I decided that I’d rather implement my own solution. I found a VPS provider at ipxcore that had a virtual server for only $1.16 a month (well within impulse buy range), so I got one.

The first thing I did was install the Debian image and boot it up. I chose Debian because I wanted a binary package manager so I didn’t have to worry about compiling a bunch of packages on a small VPS with little RAM, and I figured that Debian would have less cruft than the Ubuntu image. Unfortunately, the default Debian image included a bunch of things pre-installed that I didn’t want including Apache, a mail server, and a bunch of other services. I spent 30 minutes or so removing packages trying to get back to a bare-bones system before I decided to try the Gentoo image. Gentoo has been my Linux distribution of choice for 7 years, so I’m fairly adept at getting it configured just the way I want it. Luckily, the Gentoo image provided by ipxcore contained a very minimal set of installed packages.

Following directions found on the Free BSD Wiki and the Gentoo Wiki, I was able to get BIND installed and configured. It was a lot simpler that I thought it would be. I set up a zone file for timmontague.com that contained the SOA, NS, MX, TXT and other various records that are static and won’t change based on the dynamic IP address. I generated a cryptographic key to allow for remote DNS updates via the nsupdate command and copied it to my web server.

The first semi-difficult part was writing the script to automatically update the DNS records, in particular determining the external IP address of the web server (because it is behind a NAT). The dynamic DNS resources that I found suggested running a small PHP or Perl script in a web server that simply printed the REMOTE_ADDR. I didn’t want to install a PHP on my minimal VPS, and Perl also seemed a little overkill, so I decided to write a small CGI program in C.

#include <stdio.h>
#include <stdlib.h>
int main() {
	char* ip = getenv("REMOTE_ADDR");
	printf("Content-type: text/plain\n\n");
	if (ip)
		printf("%s\n", ip);
	return 0;
}

I installed a small web server and configured it to run CGI scripts. When I attempted to access the script from a remote location however, I kept getting 500 Internal Error messages (There was an unusual problem serving the requested URL '/myip.cgi'.). It’s exceedingly difficult to Google for such errors, but eventually I concluded that it was because the web server was sand-boxed, the binary would have to be statically linked. I recompiled it using gcc -o myip.cgi myip.c -static -L/lib -lc and started working perfectly.

I wrote a bash script on my web server to automatically determine it’s external IP address, and if it has changed, update the BIND server using nsupdate.

#!/bin/bash

DIR="/home/tim/ddns"
URL="http://ns.timmontague.com:8080/myip.cgi"
NAMESERVER="ns.timmontague.com"
TTL="10"
KEYFILE="Ktimmontague.com.+157+08540.private"
HOSTS="timmontague.com.
www.timmontague.com.
home.timmontague.com.
sync.timmontague.com."

cd $DIR
LASTIP=`cat lastip.txt`

WAN=`curl -s $URL`
if [[ $? -ne 0 ]]; then
	echo "Could not open $URL"
	exit
fi

if [[ "$LASTIP" == "$WAN" ]]; then
	exit # IP address hasn't changed
else
	echo "IP address changed from $LASTIP to $WAN"
	echo $WAN > lastip.txt
fi

TMPFILE=`tempfile`
echo "server $NAMESERVER" > $TMPFILE

for h in $HOSTS
do
	echo "update delete $h A" >> $TMPFILE
	echo "update add $h $TTL A $WAN" >> $TMPFILE
done
echo "send" >> $TMPFILE

/usr/bin/nsupdate -k "$KEYFILE" $TMPFILE
rm $TMPFILE

I set up cron to run the script every minute and it seems to be working fine, but so far (the last 4 days), the dynamic IP address hasn’t changed. I set the TTL to be 10 seconds, so when it does change, the downtime should be minimal.

The last step was updating the nameserver on my domain registrar’s website. I updated the nameserver to ns.timmontague.com, and added a glue record for ns.timmontague.com to 68.171.100.161 (the static IP of my VPS). I waited a few hours for the changes to propagate, and it worked!

All in all, setting up my own nameserver was much easier than expected. It only took a few hours to configure and install the BIND DNS server on a bare-bones VPS. In the end, my VPS is still incredibly small; the only extra packages I installed are:

app-admin/logrotate
app-admin/sudo
app-admin/sysklogd
app-editors/vim
mail-client/mailx
net-dns/bind
net-dns/bind-tools
net-firewall/iptables
sys-process/vixie-cron
www-servers/thttpd

I also set up backups on my VPS containing /etc/, /var/www/ns, and /home/tim. After gzip, the backups are less than 150kB. It’s nice knowing that if I ever need to switch VPS providers, I will be able to get another name server up and running with a minimal amount of effort.