DynDNS with NetworkManager and knot DNS (CentOS 7/8)
Having two servers from which only one has long term stable IP address which make it inconvenient when trying to reach it every time it changes IP. To deal with this I have used before a free DynDNS service hosted on Internet, but to remove dependency on it I wanted to try configure this completely “in-house”. I’m running my DNS zone on knot DNS server on CentOS 7 and I have a server with non-stable IP address running CentOS 8.
- 0. Overall architecture
- 1. Installing and configuring basic knot DNS server on CentOS 7
- 2. Configuring knot DNS server to allow updates authenticated via TSIG
- 3. Configuring client (NetworkManager) to update DNS record on IP change
- 4. Testing the setup
- 5. Conclusion
0. Overall architecture
The solution I’m running consists of knot DNS server that allows client (my other server) to update single DNS record when provided with correct TSIG key. The client running NetworkManager executes script each time the DHCP configuration has changed and updates the IP address when it thinks that it has changed according to local knowledge (file containing IP from previous run in temporary local file system).
1. Installing and configuring basic knot DNS server on CentOS 7
Knot DNS packages are available from EPEL repository on CentOS so the installation is as simple as enabling epel repository and installing the knot
package.
# yum install epel-release
# yum install knot
After installation lets create a testing zone example.com
in file /var/lib/knot/example.com.zone
with content below. Note that we create a minimal zone with definition of one name server (dns.example.com
) and 2 records that holds public SSH keys of client. These SSHFP records are optional and are here to demonstrate that we can restrict the update of DNS record to particular DNS record type. In such case you can have client with SSH key that is always same and cannot be changed while allowing client to update its A
DNS record.
# cat /var/lib/knot/example.com.zone
example.com. 86400 SOA dns.exmaple.com. ondrej-aighe0je.famera.cz. 2019120700 3600 15 86400 3600
example.com. 86400 NS dns.example.com.
dns 86400 A 192.168.2.51
client 86400 SSHFP 4 1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
client 86400 SSHFP 4 2 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
Update knot DNS /etc/knot/knot.conf
configuration file. There are two changes when comparing content of the file to one installed by default:
- First in
server
section we set the knot DNS to listen on all IPs (0.0.0.0:53
) instead of just localhost(127.0.0.1:53
). - Second we add definition for zone
example.com
that we will later need to allow particular clients to update it.
# cat /etc/knot/knot.conf
server:
rundir: "/run/knot"
user: knot:knot
listen: [ 0.0.0.0@53, ::@53 ]
log:
- target: syslog
any: info
database:
storage: "/var/lib/knot"
remote:
acl:
template:
- id: default
storage: "/var/lib/knot"
file: "%s.zone"
zone:
- domain: example.com
file: "example.com.zone"
Enable dns
service on firewalld, enable knot
systemd service and start knot DNS server.
# firewall-cmd --add-service=dns --permanent
success
# firewall-cmd --add-service=dns
success
# systemctl enable knot
Created symlink from /etc/systemd/system/multi-user.target.wants/knot.service to /usr/lib/systemd/system/knot.service.
# systemctl start knot
# systemctl status knot
● knot.service - Knot DNS server
Loaded: loaded (/usr/lib/systemd/system/knot.service; enabled; vendor preset: disabled)
Active: active (running) since Sat 2019-12-07 16:47:00 KST; 1min ago
Docs: man:knotd(8)
man:knot.conf(5)
man:knotc(8)
Main PID: XXXX (knotd)
CGroup: /system.slice/knot.service
└─XXXX /usr/sbin/knotd
Dec 07 16:47:00 fastvm-centos-7-7 knotd[XXXX]: info: binding to interface 0.0.0.0@53
Dec 07 16:47:00 fastvm-centos-7-7 knotd[XXXX]: info: binding to interface ::@53
Dec 07 16:47:00 fastvm-centos-7-7 knotd[XXXX]: info: loading 1 zones
Dec 07 16:47:00 fastvm-centos-7-7 knotd[XXXX]: info: [example.com.] zone will be loaded
Dec 07 16:47:00 fastvm-centos-7-7 knotd[XXXX]: info: starting server
Dec 07 16:47:00 fastvm-centos-7-7 knotd[XXXX]: info: [example.com.] zone file parsed, serial 201...700
Dec 07 16:47:00 fastvm-centos-7-7 knotd[XXXX]: info: [example.com.] loaded, serial none -> 20191...tes
Dec 07 16:47:00 fastvm-centos-7-7 knotd[XXXX]: info: server started in the foreground, PID 9271
Dec 07 16:47:00 fastvm-centos-7-7 knotd[XXXX]: info: control, binding to '/run/knot/knot.sock'
Dec 07 16:47:00 fastvm-centos-7-7 systemd[1]: Started Knot DNS server.
Hint: Some lines were ellipsized, use -l to show in full.
If the server is running and status of it look similar to above, we can test from other machine if we can resolve the records from example.com
domain. From some other machine test commands below. Command host
is available from bind-utils
package on CentOS 7/8, you can use any other command that can query DNS from specific host such as dig
or kdig
instead.
# host -t A dns.example.com. 192.168.2.51
Using domain server:
Name: 192.168.2.51
Address: 192.168.2.51#53
Aliases:
dns.example.com has address 192.168.2.51
# host -t NS example.com. 192.168.2.51
Using domain server:
Name: 192.168.2.51
Address: 192.168.2.51#53
Aliases:
example.com name server dns.example.com.
2. Configuring knot DNS server to allow updates authenticated via TSIG
Now as we have zone containing records we can configure server to accept changes from client that provides us with right TSIG key. Lets start by generating the key. For key generation we will use the keymgr
utility that is part of knot
package that is already installed on our sample DNS server. To generate a key using the hmac-sha512
algorithm we can use command below.
# keymgr -t client_abc_key hmac-sha512
...
# hmac-sha512:client_abc_key:aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffffgggggggggghhhhhhhhhhiiiiiiii
key:
- id: client_abc_key
algorithm: hmac-sha512
secret: aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffffgggggggggghhhhhhhhhhiiiiiiii
...
Above command generated for us two very useful outputs.
- First line is string that we can use on client to authenticate with
nsupdate
utility, - Another lines contain yaml block that we can use directly in knot DNS configuration file (
/etc/knot/knot.conf
).
Lets configure the server to accept the change of A
record type for hostname client.example.com
on our sample DNS server. We will add following things:
key
block generated by key-generating utility to register TSIG key with DNS serveracl
describing which action can be done with key and to which records (update
)- and finally the
example.com
zoneacl
will list this newly created ACL so server knows that it applies to this zone Resulting/etc/knot/knot.conf
file will look as shown below.
# cat /etc/knot/knot.conf
server:
rundir: "/run/knot"
user: knot:knot
listen: [ 0.0.0.0@53, ::@53 ]
log:
- target: syslog
any: info
database:
storage: "/var/lib/knot"
remote:
key:
- id: client_abc_key
algorithm: hmac-sha512
secret: aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffffgggggggggghhhhhhhhhhiiiiiiii
acl:
- id: client_abc_acl
key: client_abc_key
action: [ update ]
update-type: [ A ]
update-owner: name
update-owner-name: [ client.example.com ]
update-owner-match: equal
template:
- id: default
storage: "/var/lib/knot"
file: "%s.zone"
zone:
- domain: example.com
file: "example.com.zone"
acl: [ client_abc_acl ]
After changing the configuration lets reload the knot
systemd service.
# systemctl reload knot
# systemctl status knot
...
Dec 07 16:49:18 fastvm-centos-7-7 systemd[1]: Reloading Knot DNS server.
Dec 07 16:49:19 fastvm-centos-7-7 knotd[XXXX]: info: control, received command 'reload'
Dec 07 16:49:19 fastvm-centos-7-7 knotd[XXXX]: info: reloading configuration file '/etc/knot/knot.conf'
Dec 07 16:49:19 fastvm-centos-7-7 knotd[XXXX]: info: configuration reloaded
Dec 07 16:49:19 fastvm-centos-7-7 knotc[YYYY]: Reloaded
Dec 07 16:49:19 fastvm-centos-7-7 systemd[1]: Reloaded Knot DNS server.
...
We can also check that currently running configuration contains references to ‘client_abc’ with command below.
# knotc conf-read|grep client_abc
key.id = client_abc_key.
key[client_abc_key.].algorithm = hmac-sha512
key[client_abc_key.].secret = aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffffgggggggggghhhhhhhhhhiiiiiiii
acl.id = client_abc_acl
acl[client_abc_acl].key = client_abc_key.
acl[client_abc_acl].action = update
acl[client_abc_acl].update-owner = name
acl[client_abc_acl].update-owner-match = equal
acl[client_abc_acl].update-owner-name = client.example.com.
acl[client_abc_acl].update-type = A
zone[example.com.].acl = client_abc_acl
3. Configuring client (NetworkManager) to update DNS record on IP change
For the client we would need following information:
- TSIG string = first line generated by
keymgr
utility - DNS server IP address or hostname - in example here it will be IP
192.168.2.51
- DNS record that we will be changing - in example here it will be
client.example.com
- name of the network interface on which the IP is assigned
Client will also need nsupdate
utility (part of bind-utils
package) to send update to DNS.
# yum install bind-utils
To get NetworkManager update the DNS record for us we will create following script in /etc/NetworkManager/dispatcher.d/12-ddns.sh
. Important:: Adjust the variables in the “configurable variables” block of the script to match your settings. You can create script by hand (copy&paste from this page) or download it from git repository.
# curl https://raw.githubusercontent.com/OndrejHome/tp/master/2019-12-ddns-knot-dns-network-manager/12-ddns.sh > /etc/NetworkManager/dispatcher.d/12-ddns.sh
# cat /etc/NetworkManager/dispatcher.d/12-ddns.sh
#!/bin/sh
## == configurable variables == BEGIN
MONITORED_DEVICE="eth0"
TSIG_STRING="hmac-sha512:client_abc_key:aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffffgggggggggghhhhhhhhhhiiiiiiii"
DNS_SERVER="192.168.2.51"
DNS_HOSTNAME="client.example.com"
## == configurable variables == END
if [ "$1" != "$MONITORED_DEVICE" ] || [ "$2" != "dhcp4-change" ] && [ "$2" != "up" ]
then
# if we have not matched the MONITORED_DEVICE or
# if the action was not the change of IP address, then lets quietly exit
exit 0;
fi
# we have matched the event that IP was changed (or acquired) from DHCP
# first lets make sure we have tool to send updated IP to DNS
if ! command -v nsupdate >/dev/null; then
logger "ddns: Missing 'nsupdate' command: NOT updating DNS."
exit 0;
fi
# get IP address that we have set previously (this will be empty if run first time)
IP_OLD=$(cat /run/NetworkManager/current_ipv4 2>/dev/null)
# extract IPv4 address
IP=$(echo "$IP4_ADDRESS_0"|cut -d/ -f1)
# check if IPv4 address changed from previous time, continue only if there was change
# (or when this is the first time running = when IP_OLD is empty
if [ "$IP_OLD" = "$IP" ]; then
logger "ddns: No change in IP address, not updating DNS."
exit 0
fi
# update IPv4 into DNS
logger "ddns: Attempting to update IP to '$IP'."
nsupdate -y "$TSIG_STRING" <<EOF
server $DNS_SERVER
update delete $DNS_HOSTNAME a
update add $DNS_HOSTNAME 300 a $IP
send
EOF
# store updated IPv4 address in temporary localfile so we skip update on DHCP refresh for same IP
echo "$IP" > /run/NetworkManager/current_ipv4
Change the permission on script file to make it both executable
and protected from non-root users as the file contains the TSIG key that can change the DNS record.
# chmod 0700 /etc/NetworkManager/dispatcher.d/12-ddns.sh
4. Testing the setup
Easiest way to test if everything is working is to restart network on client (or simply reboot client) and check the logs. If the update was successful the logs will look similar to following.
On DNS server
# journalctl -f -u knot
...
Dec 07 18:20:48 fastvm-centos-7-7 knotd[XXXX]: info: [example.com.] DDNS, processing 1 updates
Dec 07 18:20:48 fastvm-centos-7-7 knotd[XXXX]: info: [example.com.] DDNS, finished, serial 2019120700 -> 2019120701, 0.04 seconds
Dec 07 18:20:48 fastvm-centos-7-7 knotd[XXXX]: info: [example.com.] zone file updated, serial 2019120700 -> 2019120701
...
On server you would see that new zone file was generated by knot-DNS server and that it contains the IP of client.
# cat /var/lib/knot/example.com.zone
;; Zone dump (Knot DNS 2.9.1)
example.com. 86400 SOA dns.exmaple.com. ondrej-aighe0je.famera.cz. 2019120701 3600 15 86400 3600
example.com. 86400 NS dns.example.com.
client.example.com. 86400 SSHFP 4 1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
client.example.com. 86400 SSHFP 4 2 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
client.example.com. 300 A 192.168.2.52
dns.example.com. 86400 A 192.168.2.51
;; Written 6 records
;; Time 2019-12-07 18:20:48 KST
On client
# journalctl -f
...
Dec 07 18:20:49 fastvm-centos-8-0 nm-dispatcher[XXXX]: req:4 'connectivity-change': new request (2 scripts)
Dec 07 18:20:49 fastvm-centos-8-0 root[YYYY]: ddns: Attempting to update IP to '192.168.2.52'.
Dec 07 18:20:49 fastvm-centos-8-0 nm-dispatcher[XXXX]: req:4 'connectivity-change': start running ordered scripts...
...
On client you should also be able to see file created by script containing current IPv4 address.
# cat /run/NetworkManager/current_ipv4
192.168.2.52
Over the time you may also observe following message on the client if the IP has not changed since last time script run on client.
# journalctl -f
Dec 07 18:24:53 fastvm-centos-8-0 nm-dispatcher[XXXX]: req:4 'connectivity-change': new request (2 scripts)
Dec 07 18:24:53 fastvm-centos-8-0 root[YYYY]: ddns: No change in IP address, not updating DNS.
Dec 07 18:24:53 fastvm-centos-8-0 nm-dispatcher[XXXX]: req:4 'connectivity-change': start running ordered scripts...
5. Conclusion
With above setup you can use the self-hosted DynDNS with knot DNS as server allowing client to update only records that it is allowed to by specifying the TSIG key assigned to client. Client will update the IP address on every boot of system (because the file /run/NetworkManager/current_ipv4
in temporary /run
file system would not exist) and also each time the new IP is received from DHCP. Server will automatically bump the zone ‘serial number’ and reload the zone file to reflect new change.
If you find the script interesting or you have ideas for improvement, feel free to check out OndrejHome/tp (tiny projects) repository or/and open Issue or Pull request agains it.
Version of packages used in examples
# rpm -qa|grep -E '(knot|bind-utils)'
knot-libs-2.9.1-1.el7.x86_64
knot-2.9.1-1.el7.x86_64
bind-utils-9.11.4-9.P2.el7.x86_64
# rpm -qa |grep -E '(NetworkManager|bind-utils)'
bind-utils-9.11.4-17.P2.el8_0.1.x86_64
NetworkManager-team-1.14.0-14.el8.x86_64
NetworkManager-tui-1.14.0-14.el8.x86_64
NetworkManager-1.14.0-14.el8.x86_64
NetworkManager-libnm-1.14.0-14.el8.x86_64