Homebrew Dynamic DNS with the Linode API

Posted by Orville Bennett on 18 September 2017
Read time: about 6 minutes

Over the years I've set up a variety of private cloud services. In addition to gaining expertise in these services for clients who would rather rely on their own infrastructure, it's also made me less dependent on web company services which could (and have) disappeared at inconvenient times. This desire for virtual independence has led to me learning how to set up, run and administer my own mail server (goodbye gmail!), git server, read-it-later server (goodbye Pocket!), rss feed sync server (goodbye Feedly), and even a document sync server (goodbye Dropbox). I want some of these services to always be accessible. Those services I host with Linode.

Others—the sync servers—may contain private information I don't want on someone else's infrastructure (e.g. Linode). Luckily, these are also services I don't always need access to, "most of the time" is more than good enough. If a sync server is not available when a sync action is requested, the sync client will simply try again later.

I host those sync services from a server under my control, with a residential connection. The problem with this setup is that the IP address may potentially change at any time. As I don't want to go into every device and change settings whenever an IP address changes, I've assigned this IP address a domain name. This way IP address changes only have to be updated in one location1.

I could do better though. I was still waiting for things to break before I act. I could be a bit more proactive. There is such a thing as dynamic DNS which will detect when a change to an IP address for a domain happens, and then update the DNS entry. There is one gotcha to this plan, and it's that Linode has a very functional, but very basic DNS manager. They have no dynamic DNS client that I know of so that seemed to be out of the question. Or it was, until I happened across a very useful article on the interwebs. One Travis Maynard wrote a post on how to get Dynamic DNS working with the Linode API.

Below are my modified instructions on how to get it working with my home-grown opnsense firewall/router. Before you ask: Yes, I could set it up on any machine. However, the router is the only one that is guaranteed to be connected to the internet all the time.

So let's get to it. As explained in the article, if you're using a Linode there is some information you'll need to find out before you can get this all to work.

Create a new user

  1. Login to your Linode account and navigate to Accounts and then select the Users and Permissions tab.
  2. Create another user by selecting the Add a User option.
  3. Make sure that the Yes - this user can only do what I specify option is selected and then Save Changes to create the user.

Give new user specific permissions on desired domains

The next screen will display all of the options that you have to grant or restrict to the user. In this case, all you want to do is check the "All Privs" checkbox on the domain that you've added underneath the DNS Zone Grants section. This will prevent the user (and consequentially the API key) from making changes to anything but the domain we have specified.
— Travis Maynard

Create a Linode API key for the new user

  1. Login to your Linode as the new user and go to My Profile.
  2. Select the API Keys tab.
  3. Create a new API Key.
  4. Record the API Key for later use.

Find out DOMAINID and RESOURCEID

To find the domain ID:

  1. In a browser, navigate to the URL2: https://api.linode.com/?api_key=MY_API_KEY&api_action=domain.list replacing the MY_API_KEY portion with your actual API Key.

  2. Locate the DOMAINID for the domain that will have its IP address updated dynamically.

  3. Using the newly obtained DOMAINID, modify the URL to the form https://api.linode.com/?api_key=MY_API_KEY&api_action=domain.resource.list&domainid=DOMAIN_ID and navigate to it in a browser again.

  4. Search for the subdomain you want to update based on the NAME property, and copy the RESOURCEID.

  5. Modify the URL, and add the new RESOURCEID to the end of it: &resourceid=RESOURCE_ID

  6. Navigate to this URL in a browser to check that it's correct.

To build the full URL neccessary we need some more changes. The near final form is:

https://api.linode.com/?api_key=MY_API_KEY&api_action=domain.resource.update&domainid=DOMAIN_ID&resourceid=RESOURCE_ID&target=[remote_addr]

What we've done is to change the domain.resource.list portion of the URL to domain.resource.update, while appending &target=[remote_addr] to the end of the URL.

  • The &target=[remote_addr] addition will obtain the external IP address of the machine making the request (your computer/router).
  • The domain.resource.update will cause the changes to be recorded in Linode's DNS manager.

After visting the URL in the browser you'll be able to verify that an actual update happened when you check the Last Modified date for the affected Domain Zone in the DNS Manager.

Setup cron to run command periodically

Once you have verified that the update does in fact happen, it's time to automate this. I'll be using the tool cron to periodically execute a script that visits the URL we crafted above. The script will use the curl command to access the URL.

For use with curl, we have to escape the target value's square brackets, and put the entire URL in quotes. Here is the complete URL:

https://api.linode.com/?api_key=MY_API_KEY&api_action=domain.resource.update&domainid=DOMAIN_ID&resourceid=RESOURCE_ID&target=\[remote_addr\]

This is the command cron will run:

curl 'https://api.linode.com/?api_key=MY_API_KEY&api_action=domain.resource.update&domainid=DOMAIN_ID&resourceid=RESOURCE_ID&target=\[remote_addr\]'

And finally, here is what we paste into the crontab file, after accessing it with the command crontab -e in a terminal:

*/30 * * * * /bin/echo `/bin/date`: `curl 'https://api.linode.com/?api_key=MY_API_KEY&api_action=domain.resource.update&domainid=DOMAIN_ID&resourceid=RESOURCE_ID&target=\[remote_addr\]'` >> /var/log/linode_dyndns.log

The cron settings above will run the curl command every 30 minutes and log the result to the linode_dyndns.log file in the /var/log directory. Or rather, it will if the user running this cron job has write access to the directory. If not, create the file and make it accessible to the user. Alternatively, change the file path to somewhere the user has permission to write to.

1

A wonderful side effect of this system is that it takes my downtime from multiple hours after I've noticed there is a problem with the server (i.e. IP address has changed), to maybe an hour after I've noticed there is a problem.

2

Uniform Resource Locator, i.e. a website address.