My laptop’s hostname is dent. I want my DNS records to point to that laptop whether I’m connected to my LAN directly (via WIFI or ethernet cable) or via OpenVPN (my VPN of choice).
SIDE NOTE: You will see references to nsupdate -k below. Note that in recent versions of this program, the option you want has changed to nsupdate -y. You will find an example of the new format later in this post.
My first thoughts on these were a couple of year ago, and I foolishly tried changing my VPN from routing to bridging. I failed. Terribly. I was out of town and had to get a friend to drop by and reboot my gateway for me (thanks Jerry).
Why is having the DNS correct important to me? So my backups work. That’s just for starters. My laptop actually has two hostnames; dent and dent-vpn (assigned via the VPN). These IP addresses are on different subnets. At present, if I’m away and using the VPN, I go into the Bacula configuration files and change the FQDN in to reflect dent-vpn. This works, but there is a better solution. Dynamic DNS.
NOTE: backups in my world are controlled centrally. The backup server initiates backups and thus must know the IP address of each client.
Dynamic DNS allows you to update DNS records on the fly by sending commands to your name server.
Big WARNING
What I am doing here is updating my own name servers for my own domain. If you have a dynamic IP address at home, and you’re trying to use DYNDNS on that, this article is not what you want. You want a third party service, such as NOIP.com.
Reading
I found these to be useful:
- Dynamic DNS and DHCP – Easy to do, and you’ll thank yourself later – this led me to understand how DHCP can instruct DNS
- OpenVPN man page – it was here that I discovered the learn-address hook
- DNS update script – this is the script for updating DNS records; invoked by OpenVPN
- OpenVPN environmental variables – very useful information when debugging the script
- Another DNS & DHCP strategy, this one with more restrictions; useful to understand
The tools
I am using OpenVPN and bind to achieve my goals. If you aren’t using those tools, perhaps this article will still be of some use.
DNS server configuration
To configure my DNS server so that I could use dynamic DNS, I used instructions suggested by a post in the FreeBSD Forums. I will outline the steps here.
Create your secret key
This shared secret key will be used by both bind and OpenVPN.
On my OpenVPN server, I created a new directory, and created the secret key:
mkdir /usr/local/openvpn chown openvpn:openvpn /usr/local/openvpn chmod 750 /usr/local/openvpn ch /usr/local/openvpn dnssec-keygen -a hmac-md5 -b 128 -n USER Kopenvpnupdate chmod 700 Kopenvpnupdate*
This will result in two files, a .key file, and a .private file. Here are some test files I created, but never used:
# ls Kkopenvpnupdate.+157+50354.key Kkopenvpnupdate.+157+50354.private # cat Kkopenvpnupdate.+157+50354.key Kopenvpnupdate. IN KEY 0 3 157 gBOAX68PXamnGH4RYaItHQ== # cat Kkopenvpnupdate.+157+50354.private Private-key-format: v1.3 Algorithm: 157 (HMAC_MD5) Key: gBOAX68PXamnGH4RYaItHQ== Bits: AAA= Created: 20130820220748 Publish: 20130820220748 Activate: 20130820220748
Do not use these files. For security, create your own.
I will refer to these files later.
named.conf
The following steps were performed on my DNS server.
NOTE: Be aware: this will rewrite files on disk. That is, your existing zone files will be rewritten by named. If your zone files are under revision control, you should be aware of this. For example, my zone files looked like this after my testing below:
+$TTL 3600 ; 1 hour +60 PTR dent.example.org.
I added these entries to named.conf (in my case, /var/named/etc/namedb/named.conf):
key openvpnupdate { algorithm hmac-md5; secret "gBOAX68PXamnGH4RYaItHQ=="; };
That secret is from the previous step. Copy and paste it into your named.conf file.
This key will be used to update two zones. The forward DNS and the reverse DNS.
This is the forward DNS:
zone "example.org" { type master; file "zones/example.org.db"; allow-update { key openvpnupdate; }; };
The particular may vary, but the important part is the allow-update phrase, as seen above, and as shown below in the reverse DNS:
zone "0.0.10.in-addr.arpa" { type master; file "zones/example.org.rev.db"; allow-update { key openvpnupdate; }; };
After making those changes, you want to restart named:
# service named restart
OpenVPN configuration
Back to the OpenVPN server now. In the /usr/local/openvpn directory, I added this file.
WARNING: You may not want to invoke this script for all hosts which connect via OpenVPN. I want to do this only for my laptop.
#!/bin/sh # # # original script found at http://openvpn.net/archive/openvpn-users/2005-08/msg00146.html # contribued by Charles Duffy <cduffy@xxxxxxxxxxx> Thu, 11 Aug 2005 19:07:45 -0500 # DNSSERVER="10.0.0.73" ## your DNS server FWDZONE="example.org" ## forward resolution zone (ie. vpn.company.com) REVZONE="0.0.10.in-addr.arpa" ## reverse resolution zone (ie. "1.0.0.in-addr.arpa") NSUOPTS="-k /usr/local/openvpn/Kkopenvpnupdate.+157+50354.privatey" ## extra arguments for nsupdate (ie. "-k /path/to/key") if [ -n "$DEBUG" ] ; then NSUOPTS="$NSUOPTS -d" set -x fi reverseRecord() { echo $1 | sed -re 's/^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/\4.\3.\2.\1.in-addr.arpa./' } addRecord() { local ADDRESS="$1" local CN="$2" local TEMPFILE=$(mktemp /tmp/nsupdate.XXXXXX) local REVERSE=$(reverseRecord $ADDRESS) cat >$TEMPFILE <<EOF server $DNSSERVER zone $FWDZONE update delete ${CN}. A update add ${CN}. 3600 A $ADDRESS send EOF if [ -n "$DEBUG" ] ; then cat $TEMPFILE; fi nsupdate $NSUOPTS $TEMPFILE cat >$TEMPFILE <<EOF server $DNSSERVER zone $REVZONE update delete $REVERSE PTR update add $REVERSE 3600 PTR $CN. send EOF if [ -n "$DEBUG" ] ; then cat $TEMPFILE; fi nsupdate $NSUOPTS $TEMPFILE rm -f $TEMPFILE } removeRecord() { local ADDRESS="$1" local CN="$2" local TEMPFILE=$(mktemp /tmp/nsupdate.XXXXXX) local REVERSE=$(reverseRecord $ADDRESS) cat >$TEMPFILE <<EOF server $DNSSERVER zone $FWDZONE update delete ${CN}. A send EOF if [ -n "$DEBUG" ] ; then cat $TEMPFILE; fi nsupdate $NSUOPTS $TEMPFILE cat >$TEMPFILE <<EOF server $DNSSERVER zone $REVZONE update delete $REVERSE PTR send EOF if [ -n "$DEBUG" ] ; then cat $TEMPFILE; fi nsupdate $NSUOPTS $TEMPFILE rm -f $TEMPFILE } getCN() { local IPADDR=$1 local FULLNAME=$(dig +noadditional +noqr +noquestion +nocmd +noauthority +nostats +nocomments -x ${IPADDR} | gawk '{print $5}') if [ -n "$FULLNAME" ] ; then echo $FULLNAME | sed -re 's/\.$//' return 0 else return 1 fi } case "$script_type" in learn-address) OPERATION=$1 ADDRESS=$2 CN=$3 if [ "$CN" != "dent.example.org" ] ; then if [ -n "$DEBUG" ] ; then echo "not a host I work on"; fi exit; fi REVERSE=$(reverseRecord $ADDRESS) case "$OPERATION" in add|update) addRecord "$ADDRESS" "$CN" ;; delete) CN=$(getCN "$ADDRESS") removeRecord "$ADDRESS" "$CN" ;; *) echo "ERROR: don't know operation \"$OPERATION\"." exit 1 esac ;; *) echo "\"${script_type}\" not handled" esac return 0
Here is what we have in this directory now. Make sure your chmod and chown are similar to this:
# ls -l total 14 -rw------- 1 openvpn openvpn 56 Aug 20 23:07 Kkopenvpnupdate.+157+50354.key -rw------- 1 openvpn openvpn 165 Aug 20 23:07 Kkopenvpnupdate.+157+50354.private -rwxr----- 1 openvpn openvpn 2474 Aug 20 22:21 learn-address
You should test this program from the command line before proceeding.
Testing nsupdate
I restarted named in a previous section. Now that we have everything else configured, it is time to start testing. Let’s see if named will accept updates via nsupdate, which is what the learn-address script will invoke.
Here is a very simple test which checks the current IP address, changes it, then checks it again. Keep in mind the following points:
- The host I am on has 10.0.0.76 listed as the first nameserver in /etc/resolv.conf
- The DNS server at 10.0.0.76 is authoriative for the domain example.org (the one I am changing)
# host dent.example.org dent.example.org has address 10.0.0.121 # nsupdate -k /usr/local/openvpn/Kkopenvpnupdate.+157+50354.key > server 10.0.0.73 > zone example.org > update delete dent.example.org > update add dent.example.org 3600 A 10.0.0.122 > send > quit # host dent.example.org dent.example.org has address 10.0.0.122
This demonstrates that updates via nsupdate can be done.
Testing learn-address
The learn-address script, which I place in /usr/local/openvpn, will be invokved by OpenVPN whenever a clent connects. The primary purpose of this hook seems to be validation. If the script returns a non-zero result, the connection will be aborted. That is what the documentation seems to indicate. But it also seems to be a great place from which to update our DNS records.
In this section, we’ll make sure that learn-address works before we get OpenVPN to run it.
The script uses environment variables which are set by OpenVPN. For our testing, we need to test those variables. For testing, you can also set DEBUG to non-blank value.
The following test was performed with debugging enabled. There is another test immediately below, without debugging.
# export DEBUG="d" # export script_type="learn-address" # ./learn-address update 10.0.0.60 dent.example.org + OPERATION=update + ADDRESS=10.0.0.60 + CN=dent.example.org + [ dent.example.org != dent.example.org ] + reverseRecord 10.0.0.60 + echo 10.0.0.60 + sed -re 's/^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/\4.\3.\2.\1.in-addr.arpa./' + REVERSE=60.0.0.10.in-addr.arpa. + addRecord 10.0.0.60 dent.example.org + local ADDRESS=10.0.0.60 + local CN=dent.example.org + mktemp /tmp/nsupdate.XXXXXX + local TEMPFILE=/tmp/nsupdate.teuxaT + reverseRecord 10.0.0.60 + echo 10.0.0.60 + sed -re 's/^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/\4.\3.\2.\1.in-addr.arpa./' + local REVERSE=60.0.0.10.in-addr.arpa. + cat + [ -n d ] + cat /tmp/nsupdate.teuxaT server 10.0.0.73 zone example.org update delete dent.example.org. A update add dent.example.org. 3600 A 10.0.0.60 send + nsupdate -k /usr/local/openvpn/Kkopenvpnupdate.+157+50354.key -d /tmp/nsupdate.teuxaT Creating key... Sending update to 10.0.0.73#53 Outgoing update query: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 27439 ;; flags:; ZONE: 1, PREREQ: 0, UPDATE: 2, ADDITIONAL: 1 ;; ZONE SECTION: ;example.org. IN SOA ;; UPDATE SECTION: dent.example.org. 0 ANY A dent.example.org. 3600 IN A 10.0.0.60 ;; TSIG PSEUDOSECTION: openvpnupdate. 0 ANY TSIG hmac-md5.sig-alg.reg.int. 1377094801 300 16 J/+p0YsZ5I7rm8KyEbKf2g== 27439 NOERROR 0 Reply from update query: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 27439 ;; flags: qr ra; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 1 ;; ZONE SECTION: ;example.org. IN SOA ;; TSIG PSEUDOSECTION: openvpnupdate. 0 ANY TSIG hmac-md5.sig-alg.reg.int. 1377094801 300 16 hNlGuErqtojk3acM0KMmlw== 27439 NOERROR 0 + cat + [ -n d ] + cat /tmp/nsupdate.teuxaT server 10.0.0.73 zone 0.0.10.in-addr.arpa update delete 60.0.0.10.in-addr.arpa. PTR update add 60.0.0.10.in-addr.arpa. 3600 PTR dent.example.org. send + nsupdate -k /usr/local/openvpn/Kkopenvpnupdate.+157+50354.key -d /tmp/nsupdate.teuxaT Creating key... Sending update to 10.0.0.73#53 Outgoing update query: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 16818 ;; flags:; ZONE: 1, PREREQ: 0, UPDATE: 2, ADDITIONAL: 1 ;; ZONE SECTION: ;0.0.10.in-addr.arpa. IN SOA ;; UPDATE SECTION: 60.0.0.10.in-addr.arpa. 0 ANY PTR 60.0.0.10.in-addr.arpa. 3600 IN PTR dent.example.org. ;; TSIG PSEUDOSECTION: openvpnupdate. 0 ANY TSIG hmac-md5.sig-alg.reg.int. 1377094801 300 16 SZG8KtVdVIuTz5+2rIvmyQ== 16818 NOERROR 0 Reply from update query: ;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 16818 ;; flags: qr ra; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 1 ;; ZONE SECTION: ;0.0.10.in-addr.arpa. IN SOA ;; TSIG PSEUDOSECTION: openvpnupdate. 0 ANY TSIG hmac-md5.sig-alg.reg.int. 1377094801 300 16 O1a7hUFcvBLvin9hH5sXfw== 16818 NOERROR 0 + rm -f /tmp/nsupdate.teuxaT + return 0 [code] That's a lot to take in, so let's do that again, without debugging. [code] # export DEBUG="" # host dent.example.org dent.example.org has address 10.0.0.60 # ./learn-address update 10.0.0.61 dent.example.org # host dent.example.org dent.example.org has address 10.0.0.61 #
There, it just works. Hopefully, your experience will be as easy.
Setting the hook
These steps are performed on the OpenVPN server.
I added the following two lines to openvpn.conf on my server:
script-security 3 system learn-address /usr/local/openvpn/learn-address
If you do not add the first line, you’ll see an error such as this:
openvpn[67915]: dent.example.org/x.x.x.x:50846 WARNING: Failed running command (--learn-address): external program fork failed
You will be reminded of the above by messages such as this:
NOTE: OpenVPN 2.1 requires '--script-security 2' or higher to call user-defined scripts or executables
After making those changes, you will need to restart OpenVPN.
Connect your client
Now you should be able to connect your OpenVPN client and have your DNS updated automatically. Please, if this does not work, let me know in the comments and we’ll get it working for you.
but no DHCID, not mine
When I first tried this, I started getting this error right away:
dhcpd: Forward map from laptop.example.org to 10.55.0.60 FAILED: Has an address record but no DHCID, not mine.
I wrote about this in the FreeBSD Forums. This problem is caused by my original A record still being in the zone file. I can delete that zone file with this command (which is similar to that used in my testing above)
# nsupdate -k /usr/local/openvpn/Kkopenvpnupdate.+157+50354.key > server 10.0.0.73 > zone example.org > update delete dent.example.org > send > quit
In more recent versions of nsupdate, the command was:
# nsupdate -y openvpnupdate:gBOAX68PXamnGH4RYaItHQ==
After that change, the no DHCID messages stopped.
ADDENDA – 16 Nov 2014
In order to get this working with pfSense 2.1.5-RELEASE, I had to make a few changes.
That version of pfSense uses an older version of nsupdate. As I found in this post, I had to create my keys with -C (compatability mode) to generate keys with dnssec-keygen for use with the old nsupdate.
I was getting this error before that:
could not read key from Kkey_name.+157+18051.private: private key is invalid
Next, thanks to this post, I found out I had to alter the script to invoke nsupdate with different options:
NSUOPTS=" -y openvpnupdate:gBOAX68PXamnGH4RYaItHQ=="
instead of the old:
NSUOPTS="-k /usr/local/openvpn/Kkopenvpnupdate.+157+50354.privatey"
I was getting this error:
; TSIG error with server: tsig indicates error update failed: NOTAUTH(BADSIG)