Dynamic DNS—an Object Lesson in Problem Solving
The other day in the Linux Journal IRC room (#linuxjournal on Freenode), I was whining to the channel about no-ip.com deleting my account without warning. My home IP address hadn't changed in a couple months, and because there was no update, it appeared abandoned. The problem was, although the IP address hadn't changed, I was using the Dynamic DNS domain name to connect to my house. When the account was deleted, the domain name wouldn't resolve, and I couldn't connect to my house anymore.
The folks in the IRC channel were very helpful in recommending alternate
Dynamic DNS hosting services, and although there are still a few free
options out there, I was frustrated by relying on someone else to manage
my DNS services. So, like any nerd, I tried to figure out a way to host
a Dynamic DNS service on my own. I thought it would be a simple
install on my colocated server, but it turns out there's not a simple
Dynamic DNS server package out there—at least, not one I could find. So,
again like any particularly nerdy nerd, I decided to roll my own. It's
not elegant, it's not pretty, and it's really just a bunch of cheap hacks.
But, it's a great example of solving a problem using existing tools,
so in this article, I explain the process.
First, it's important to point out a few things. The purpose of this article is not really to explain the best way to make a self-hosted Dynamic DNS system. In fact, there probably are a dozen better ways to do the same thing. That's sort of the point. The more familiar you are with Linux tools, the more resourceful you can be when it comes to problem solving. I go through the steps I took, and hopefully most readers can take my method and improve on it several times over. That type of collaboration is what makes the Open Source community so great! Let's get started.
My Particular Toolbox
We all have a slightly different bag of tricks. I'm in the "extremely lucky" category, thanks to Kyle Rankin tipping me off about the free Raspberry Pi colocation service. A full-blown Linux box with a static IP on the Internet is truly the sonic screwdriver when it comes to these sorts of things, but perhaps someone else's solution won't require a complete server.
Along with the Raspberry Pi server, I have a Linux server at home, a home router and a handful of domains I own. I also have accounts on several Web hosting servers, and accounts with cloud-based storage like Dropbox, Google Drive and a handful of others.
First Problem—What's My IP?
The beauty of Dynamic DNS hosting is that regardless of what your home IP address is, the same domain name will resolve even if it changes. Granted, my home IP address hadn't changed for months, but I still used the DNS name to access it. Therefore, when my account at no-ip.com was deleted, I had no way to tell what my home IP was.
There are plenty of sites on the Internet that will return your IP address. Because I just had my Dynamic DNS account deleted, I really didn't want to depend on a free on-line service for detecting my IP address. Thankfully, this piece of the puzzle was simple. A few lines of PHP hosted on any Web host that supports PHP is all it takes. For example, my IP detection script is hosted at http://snar.co/ip (my personal domain—feel free to use it). See it in action in Figure 1. It contains nothing more than this:
<?php // Save the IP to a variable $ip_address = $_SERVER['REMOTE_ADDR']; // To display the IP: echo $ip_address; ?>
Figure 1. Unlike many "what is my IP" services, my script returns nothing but an IP address.
The frustrating part is that although I had a way to detect my IP address at home, I wasn't actually at home, so I had a catch-22 situation. Thankfully, my wife was home. I texted her and asked her to visit my snar.co address and let me know the IP address she got back. Once I had that IP address, I could connect to my home server and set up some automation.
Let's Never Do This Again
It's not that I don't love texting my wife, it's just that hoping someone is home to check an IP address is not the best way to roll your own DNS. There's also the possibility that whatever Dynamic DNS solution I dream up might fail, and I want to make sure I always can figure out my home IP address.
There are a couple different ways I considered for making my IP address
always accessible. The simplest was to set up a cron job to
upload regularly the results of my PHP script to my Raspberry Pi or to one
of my Web hosts. In order to do that, I'd simply have to set up SSH
keys so my home server could upload a file without any interactive
authentication. This is, in fact, what I recommend. As it happens,
however, I'm lazy. What I actually did was set up a cron job that
copied my IP address into a text file inside my Dropbox folder. It's
not a better solution than
scp-ing, but the end result is the same.
Here's what my cron job looks like:
1 * * * * /usr/bin/wget -r --quiet -O ~/Dropbox/Public/IP.txt ↪'http://snar.co/ip'
It basically updates my Dropbox folder every hour with my current home IP address. Because Dropbox syncs onto every device and computer I own, it's always readily accessible.
But That's Still Not DNS
Now things get a little complicated. Because I have a full-blown server and a handful of domains to use, it would make sense to set up BIND and serve out a subdomain. BIND does have the ability to change a host entry with a remote update command. It requires setting up encryption keys, and of course, the BIND dæmon has to be configured properly. Remember when I said I was lazy? It's still true. Because all I wanted to do was serve up a single domain name for my home IP address, I opted for something simpler.
DNSMasq is a very simple dæmon that runs on my Linux-based home routers. It handles both DHCP services and DNS resolution. In both cases, the services are very stripped down and simplistic, but if all you need is simple DNS resolution, it doesn't get simpler than DNSMasq. It will look at the server's /etc/hosts file and serve out those entries when queried. All I had to do was get my home IP address into my server's /etc/hosts file, and regularly send DNSMasq the HUP signal to reload its files. A simplistic DNS server was the final piece to the puzzle. Next came implementation.
Putting It All Together
The first step was to create a DNS entry that I could update with DNSMasq. This is simpler than most people realize. I just added an NS record pointing to my Linux server. So basically, I have an entry that looks like this:
home.mydomain.org. IN NS server.mydomain.org.
That means, "when resolving home.mydomain.org, or any subdomain of it, ask server.mydomain.org for the address." This is exactly what I want, because then any time I (or anyone else) tries to access home.mydomain.org, it will ask my server to resolve the name. The only thing left to do is to get my server, which is running DNSMasq, to respond with the proper IP address. That means a couple more cron jobs.
Remember my cool little IP.txt file I keep in my Dropbox? Well, in order to hack together the /etc/hosts file on my server, I had to modify my PHP script a little. In order to create an output compatible with /etc/hosts, I changed it to:
<?php // Save the IP to a variable $ip_address = $_SERVER['REMOTE_ADDR']; // To display the IP: echo $ip_address; echo " home.mydomain.org"; ?>
Note the space before "home". Now the file in my Public Dropbox folder is a properly formatted /etc/hosts line. In order to combine that with my original hosts file, I created a folder /etc/hosts.d/ on my server, and copied /etc/hosts to /etc/hosts.d/00-original.
Still with me? The last step is to run the following script on the server. I do this script every hour, so if my IP address changes, it should take at the most an hour before it's corrected. Here is the server script:
#!/bin/bash /usr/bin/wget -r --quiet -O /etc/hosts.d/home ↪'https://dl.dropbox.com/xxx/IP.txt' cat /etc/hosts.d/* > /etc/hosts killall -SIGHUP dnsmasq
The first line retrieves the current IP address stored in my Dropbox Public folder. The second line creates a new /etc/hosts file by concatenating all the files in /etc/hosts.d/. Then finally, I send the SIGHUP signal to dnsmasq, so it will reload the /etc/hosts file.
The thing I really like about this example as a way to demonstrate problem solving is that there are so many different ways to accomplish the same results. My solution is far from the best. Off the top of my head:
I could have the script on my home server check for a change in IP address rather than just constantly updating. If there was a change, it could start the update process on my remote server rather than updating the hosts file every hour whether it needs it or not.
Depending on what DNS hosting company you use, it's probably possible to change an address with a remote command. It's also possible there are free DNS servers out there directly supported by a client like ddclient.
Because my solution requires a remote Linux server with a static IP, it makes my specific solution inaccessible to many people. That just means you need to think harder in order to dream up a solution!
Where to Go from Here
It seems apropos to make my disclaimer again: the process I just explained is not the most efficient way to solve the problem of changing IP addresses. My methods are crude, my scripts are simplistic, and I haven't included any error correcting whatsoever. (What happens if I can't download the file from Dropbox? Will my script fail? Probably!) The purpose of this article is to make you think. Linux gives us tools that are powerful, flexible and above all else, useful. Sometimes you need to create a little digital duct tape and solve a problem on the fly.
What if you can't seem to come up with a solution to your particular problem? That's where the Linux community really shines. Stop in at the #linuxjournal channel, or attend a local LUG meeting. Folks there are much like me and are eager to help solve problems. Everyone loves a puzzle, and when you get to solve it with Linux? Awesome!