With last month seeing a 100% increase in the number of computers I own, I found it useful to be able to determine the IP address of my various devices, with a lightweight Linux-compatible solution. My initial solution was to use Syncthing and interface with its dynamic IP address detection functionality, but this proved too slow and unreliable to be useful.

Many providers offer free dynamic DNS services, but the smallest TTL supported is around 30 seconds: 30 seconds too long for my liking. With my own VPS and domain name, this seemed like a good opportunity to finally get around to setting up a DNS server. As it turns out, this is well-supported with the popular DNS server BIND.

Guide

  1. Install bind. On Arch Linux, this is accomplished by a simple pacman -S bind.

  2. Set up a zone file for the domain. I wished to delegate only a particular subdomain to my dynamic DNS server, leaving the remainder of the domains to the professionals, so I created /var/named/dynamic.example.com.zone with contents:

$TTL 2  ; 2 seconds
dynamic.example.com     IN SOA  ns1.example.com. postmaster.example.com. (
                                2017010101 ; serial
                                28800      ; refresh (8 hours)
                                1800       ; retry (30 minutes)
                                604800     ; expire (1 week)
                                86400      ; minimum (1 day)
                                )
                        NS      ns1.example.com.

I have set a TTL of 2 seconds, as Google Public DNS reduces the TTL by 1 second when it caches the result (giving a TTL of 1), and a 0 TTL causes some DNS servers to cache the result forever: not what we want.

  1. Enable the new zone by adding to /etc/named.conf:
zone "dynamic.example.com" IN {
    type master;
    file "dynamic.example.com.zone";
    allow-update { 127.0.0.1; };
    notify no;
};
  1. Enable and start the DNS server. On Arch Linux, this is accomplished by systemctl enable named and systemctl start named.

  2. Set the server as the DNS server for the (sub)domain. On Namecheap, this was accomplished by adding an NS record for dynamic pointing to ns1.example.com, and adding an A record for ns1 pointing to the IP address of the server.

  3. If all goes well, dig dynamic.example.com NS should return your DNS server.

  4. Now run nsupdate, type server 127.0.0.1 followed by enter, then type update add test.dynamic.example.com. 2 A 1.2.3.4 followed by two enters.

  5. If all goes well, the command should exit without errors, and dig test.dynamic.example.com should return 1.2.3.4 with a TTL of 1 or 2!

  6. Now all that remains is to allow the DNS records to be updated from the web. I cooked up a simple PHP script:

<?php
	header('Content-Type: text/plain');
	$subdomain = $_GET['subdomain'];
	$address = $_GET['address'];
	
	$descriptorspec = array(
		0 => array('pipe', 'r'),
		1 => array('pipe', 'w')
	);
	$process = proc_open('nsupdate', $descriptorspec, $pipes, NULL, NULL);
	
	fwrite($pipes[0], "server 127.0.0.1\n");
	fwrite($pipes[0], "update delete $subdomain.dynamic.example.com. A\n");
	fwrite($pipes[0], "update add $subdomain.dynamic.example.com. 2 A $address\n\n");
	fclose($pipes[0]);
	
	echo stream_get_contents($pipes[1]);
	
	proc_close($process);
?>

Access to the script should be controlled, for example by a .htaccess file.

  1. Call the script: curl -v -u username:password 'https://example.com/dns.php?subdomain=test&address=8.8.8.8'

  2. If all goes well, no error should be reported, and dig test.dynamic.example.com should now return 8.8.8.8!

  3. Automatic updating of the IP address can be facilitated by a NetworkManager script. Create executable /etc/NetworkManager/dispatcher.d/dns.sh with contents:

#!/bin/bash

IF=$1
STATUS=$2

if [ "$IF" == "eno1" ]; then
	case "$STATUS" in
		up)
			logger -s 'Connected to ethernet, updating DNS'
			IP=$(ip addr show dev $IF | grep 'inet ' | awk '{split($2,a,"/");print a[1];}')
			curl -v -u username:password "https://example.com/dns.php?subdomain=test&address=$IP"
		;;
		*)
		;;
	esac
fi

Replace eno1 with the desired network inferface, and replace test with the desired subdomain.

  1. Now, whenever the network interface is connected, the device should be reachable at test.dynamic.example.com.