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
# 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
serversection we set the knot DNS to listen on all IPs (
0.0.0.0:53) instead of just localhost(
- Second we add definition for zone
example.comthat 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"
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: 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
# 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
- Another lines contain yaml block that we can use directly in knot DNS configuration file (
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:
keyblock generated by key-generating utility to register TSIG key with DNS server
acldescribing which action can be done with key and to which records (
- and finally the
aclwill list this newly created ACL so server knows that it applies to this zone Resulting
/etc/knot/knot.conffile 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: 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: 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
- DNS server IP address or hostname - in example here it will be IP
- DNS record that we will be changing - in example here it will be
- 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
# 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...
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