Did you know that you can navigate the posts by swiping left and right?

Update CloudFlare DDNS with systemd timers

13 Jan 2016 . configuration . Comments

CloudFlare As a Dyanmic DNS provider

CloudFlare has some great features for Web Applications (caching, SSL termination, Reports), but you can also use it as a Dyanmic DNS server thanks to its great API. suppose you have a webserver, or VPN/SSH endpoint on your internet connection, But, your provider doesnt give you a static IP. How do you manage the DNS record? Well you could sign up a DDNS service or manually update the record… Well, you could also use CF for DNS hosting and use their API to update your record.

To keep our record up-to-date, we need to periodically update the record, incase our external IP address changes. in the past, we used cron jobs to achieve such tasks, a simple one line to call a command on predefined intervals.

Instead of cron, we can use the power of systemds’ timers to achieve the same result. If your OS supports systemd, I would recommend this over a cron job.

Why Systemd

systemd is the replacement to the older SysVInit system, systemd adds much more functionality, specifically, it has the ability to replace cron jobs, which is what we are interested in here.

My Current server OS (Debian) supports systemd out of the box, and any OS with an update in the past year should also ship with systemd (most popular distros appear to, or will shortly).

I chose not to go down the ddclient patching path (which is widely blogged and documented) as its messy and mucks with core packages, meaning security and version updates might break the patch.

Lastly, I had a python script inside cron which did the DNS updates, but this is broken (not sure why) and my python isnt great so I cant be bothered to debug it.

How does it work

Before I go into the full step by step guide, i’ll briefly go over the process and what I want to archeve by this process.

We have a simple bash script which will update our CloudFlare DNS A record with our external IP address. This script will be executed every 5mins after boot using a systemd timer.

The bash script will use a HTTP POST to the cloudflare API containing our record ID, API key, DOMAIN, Record type, Record Name and IP Address.

Setup and Installation

WARNING: Please carefully read ALL commands and scripts posted before running them, Please DO NOT blindly copy and past. I will not be responsible for data loss or OS issues.

Requirements

There are a few requirements, to get this going. You will need the following:

  • A CloudFlare account (free is fine)
  • Your DNS hosted with CloudFlare
  • Your CloudFlare API Key
  • systemd
  • An exsisting DNS record which will recieve the updates (can have a dummy IP)
  • sudo access

If you have all that… carry on.

The bash script

There are two parts to geting this working, the first, is getting your exsisting record details with an API call, then we need to use of those details to fill in the bash script.

Lets get the information we need from CloudFlare, run the following curl command to see all your DNS information.


curl https://www.cloudflare.com/api_json.html -d 'a=rec_load_all' \
-d 'tkn=YOUR_CloudFlare_API_Key' \
-d 'email=YOUR_CloudFlare_Email_Address' \
-d 'z=YOUR_DOMAINNAME (eg example.com)'

This command will return JSON, so use a JSON viewer to make it easy to read. We are interested in rec\_id and type fields for the record.

In this example, my rec_id is: 123456 and record type is A and the name is www.example.com

Lets create a bash script to make the update, create /usr/local/bin/update-cfddns.sh with the following content:


#!/bin/sh
NEW_IP=`wget -O - -q http://ifconfig.me/ip`
CURRENT_IP=`cat /var/tmp/current_ip.txt`

if [ "$NEW_IP" = "$CURRENT_IP" ]
then
        echo "No Change in IP Adddress"
else
        curl https://www.cloudflare.com/api_json.html \
          -d 'a=rec_edit' \
          -d 'tkn=YOUR_API_KEY' \
          -d '[email protected]' \
          -d 'z=example.com' \
          -d 'id=123456' \
          -d 'type=A' \
          -d 'name=www.example.com' \
          -d 'ttl=1' \
          -d "content=$NEW_IP"
        echo $NEW_IP > /var/tmp/current_ip.txt
fi

Dont forget to chmod +x the file.

Systemd timers

Systemd use two units to create a timer, Think of a unit as a action (or file), one unit defines the timer and its intervals, the second is the service (or action) we want the timer to trigger. The service will have the bash script referenced.

There are two ways to this, you could create a generic timer with a target, then service units can listen for that target and run. Alternatively, we can write a specfic timer and call the service from that. Im going to the latter of the two.

Setup Units

Create A timer file into /etc/systemd/system/CfDDNS-timer.timer with the following:


[Unit]
Description=CfDDNS DNS Update Timer

[Timer]
OnBootSec=5min
OnUnitActiveSec=5min
Unit=CfDDNS.service

[Install]
WantedBy=basic.target

What do these lines do?

  • Description: A short desc about what the timer does.
  • OnBootSec: How long after boot to run the timer for the first time.
  • OnUnitActiveSec: Interval between each run (in this case, run the timer every 5mins).
  • Unit: What to do when the timer runs.
  • WantedBy: Dependencies to help with service ordering. (theres no point running this without network)

Next, create a service into /etc/systemd/system/CfDDNS.service with the following:


[Unit]
Description=Update CfDDNS DNS Record
Wants=CfDDNS-timer.timer

[Service]
ExecStart=/usr/local/bin/update-cfddns.sh

What have we set in this service?

  • Description: A short desc about what the service does.
  • Wants: A Dependency that requires our timer to be active.
  • ExecStart: What to run when this unit is executed. (this is our actual script from before)

Setup systemd

Now we have our timer and our code to execute, systemd wont just automatically run this timer, we need to enable it and start it. To do that, we just system systemctl:


sudo systemctl enable CfDDNS-timer.timer
sudo systemctl start CfDDNS-timer.timer

Testing and Verification

Hopefully the systemd configuration and units where setup without an issue, we can verify their status using systemctl commands. We are going to check the status two ways, one is list out the timers, useful to check we are infact running every 5mins, and that we call the CfDDNS.service.

The Second one will provide more verbose output using the status param.

1: View the systemd timers


sudo systemctl list-timers

NEXT                          LEFT          LAST                          PASSED       UNIT                         ACTIVATES
Fri 2016-01-15 09:10:47 NZDT  3min 12s left Fri 2016-01-15 09:05:47 NZDT  1min 47s ago CfDDNS-timer.timer           CfDDNS.service
n/a                           n/a           Tue 2016-01-12 06:45:52 NZDT  3 days ago   ureadahead-stop.timer        ureadahead-stop.service

2: view the timer unit status:


sudo systemctl status CfDDNS-timer.timer

● CfDDNS-timer.timer - CfDDNS DNS Update Timer
   Loaded: loaded (/etc/systemd/system/CfDDNS-timer.timer; enabled; vendor preset: enabled)
   Active: active (waiting) since Tue 2016-01-12 09:45:15 NZDT; 2 days ago

Jan 12 09:45:15 elmo systemd[1]: Started CfDDNS DNS Update Timer.
Jan 12 09:45:30 elmo systemd[1]: Started CfDDNS DNS Update Timer.

Hopefully this has been useful and a good example on how to write systemd timers and units, these are also available on github in my dotfiles repo if you want the up-to-date version (incase I made changes and didnt update my blog post).