Update a Dynamic DNS IP with BIND

I wrote the following nsupdate-ddns.sh script to update the dynamic DNS entry for my laptop when switching network locations. There are several ways to execute a script like this automatically (cronjob, startup script, launcher, etc.) — I chose to use Sidekick for Mac OS X, which allows me to execute it when switching locations (either network or physical). This script can also create the private authentication key needed by the DDNS BIND server, and will display some sample configuration values. If you’re setting up a new DDNS BIND server, you can use the examples to configure your dynamic zone file.

You’ll need to edit the value of “ddns_domain” to reflect your dynamic domain name. The default value uses an FQDN in the form of hostname.ddns.domainname.com. The script also assumes the secret key file will reside in /etc/named.d/ and checkip.dyndns.org will be used to get the public (NAT’ed) IP. You may need to adjust the regex if you use another website.

Generally you would execute the nsupdate-ddns.sh script without any parameters, but you can add “–force” to force an update, “–visual” to see what the script has done, and “–keygen” to generate a new secret key file. If you already have a secret key file, the script will exit with an error when you try to overwrite it — you’ll have to remove the old key before generating a new one.

When executed, the script checks to see if a network interface is available, does a DNS lookup to find out the current DDNS IP value, retrieves the public IP from checkip.dyndns.org, and if the two IPs are different, it updates the DDNS zone. The interface availability check uses ifconfig and works on Mac OS X (status: active) and Linux (inet addr:.*Bcast:.*Mask:). You may have to adjust the regex if your ifconfig command output is different.

#!/bin/bash
#
# /usr/local/sbin/nsupdate-ddns.sh
#
# Update DDNS server with current public IP.
#
# Copyright 2012 - Jean-Sebastien Morisset - https://surniaulula.com/
#
# This script is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This script is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details at http://www.gnu.org/licenses/.

ddns_domain="ddns.MY_DOMAIN_NAME.com"
my_fqdn="`hostname -s`.$ddns_domain"
key_file="`echo /etc/named.d/K$my_fqdn.+157+*.private`"
if_active="`/sbin/ifconfig -a|egrep '(status: active|inet addr:.*Bcast:.*Mask:)'`"
ip_check_url="http://checkip.dyndns.org/"	# website that displays our IP (see regex bellow)

# read command line opts (works better than getopt)
while :
do
	for arg in "$@"
	do
		case $arg in
			-f|--force)
				force="1"
				shift 1
				;;
			-h|--help)
				echo "purpose: update $my_fqdn ddns entry with current public ip."
				echo " syntax: $0 [--help|--force|--visual|--keygen]"
				exit 0
				;;
			-k|--keygen)
				if [ -f "$key_file" ]
				then
					echo "  error: $key_file already exists!"
					exit 1
				fi
				mkdir -p /etc/named.d	# just in case
				cd /etc/named.d && dnssec-keygen -a HMAC-MD5 -b 512 -n USER $my_fqdn.
				key_file="`echo /etc/named.d/K$my_fqdn.+157+*.private`"
				if [ ! -f "$key_file" ]
				then
					echo "  error: $key_file file not found!"
					exit 1
				fi
				chmod 600 $key_file	# just to make sure
				secret="`sed -n 's/^Key: //p' $key_file`"
				if [ -z "$secret" ]
				then
					echo "  error: secret key not found in $Key_file!"
					exit 1
				fi
				cat <<EOF

The following instructions are provided as an example. The file names, their
location, and content may vary depending on the DNS server configuration. The
/etc/named.d/keys.conf file shown in this example is included by
/etc/named.conf and owned by root:named with permissions 640 (only root and the
named group members can view the secret keys).

1) Add the following to /etc/named.d/keys.conf:

	key $my_fqdn. {
		algorithm "HMAC-MD5";
		secret "$secret";
	};

2) Add the following to the $ddns_domain zone definition 
update-policy section:

	grant "$my_fqdn." name $my_fqdn. A TXT;

Here's an example of a complete zone definition for $ddns_domain:

	zone "$ddns_domain" in { 
		type master;
		file "master/db.$ddns_domain";
		allow-transfer { acl_lan_slaves; acl_isp_slaves; };
		update-policy { 
			grant "$my_fqdn." name $my_fqdn. A TXT;
		};
	};

EOF
				exit 0
				;;
			-v|--visual)
				visual="1"
				shift 1
				;;
			*)	args[$(( i++ ))]="$1"
				shift 1
				;;
		esac
		continue 2
	done
	break
done

# reset $1, $2, etc. with left-over paramters
set -- "${args[@]}"

if [ -z "$if_active" ]
then
	echo "warning: no active network interface found."
	exit 0
elif [ ! -f "/etc/resolv.conf" ]
then
	echo "  error: /etc/resolv.conf file missing!"
	exit 1
elif [ ! -f "$key_file" ]
then
	echo "  error: $key_file file not found!"
	echo "   info: use --keygen to generate the private/public key pair."
	exit 1
fi

[ -z "$ip_old" ] && ip_old="`/usr/bin/host $my_fqdn.|sed -n 's/^.*has address \([0-9][0-9\.]*\)$/\1/p'`"
[ -z "$ip_new" ] && ip_new="`/usr/bin/curl --silent --max-time 15 $ip_check_url | \
	sed -n 's/^.*IP Address: *\([0-9][0-9\.]*\)<.*$/\1/p'`"

[ -n "$visual" ] && echo "old ip: $ip_old"
[ -n "$visual" ] && echo "new ip: $ip_new"

if [ -z "$ip_old" ]
then
	echo "  error: cannot determine current ddns ip address!"
	exit 1
elif [ -z "$ip_new" ]
then
	echo "  error: cannot determine public ip address!"
	exit 1
elif [ "$ip_new" = "$ip_old" ]
then
	[ -n "$visual" ] && echo "  info: current ddns ip and public ip are identical."
	[ -n "$visual" -a -n "$force" ] && echo ""
elif [ "$ip_new" != "$ip_old" ]
then
	[ -n "$visual" ] && echo "  info: current ddns ip and public ip are different."
	[ -n "$visual" ] && echo ""
fi

if [ -n "$force" -o "$ip_new" != "$ip_old" ]
then
	{ 
		echo "server $ddns_domain"
		echo "zone $ddns_domain"
		echo "update delete $my_fqdn. A"
		echo "update add $my_fqdn. 10 A $ip_new"
		[ -n "$visual" ] && echo "show"
		echo "send"
	} | /usr/bin/nsupdate -k $key_file || exit $?
fi

exit 0

Download the nsupdate-ddns.sh script.

Find this content useful? Share it with your friends!