Automated Dynamic DNS updating on Raspberry Pi

I use a Raspberry Pi to host SVN and Git repositories but don’t have a static IP with my BT Infinity account. To support remote connectivity I use a dynamic DNS service from Dyn.

The only feature missing was the ability to detect when the IP address had changed, and then to automatically update it.

So with a bit lot of Googling I’ve put together a Python 3 script which is run hourly via crontabThe script uses Dyn’s IP address utility to find the current IP address. This is compared to the last known IP address which I store in a text file. If the address has changed then Dyn’s Perform Update utility is used to sent the new IP address. Any changes or errors are emailed back to me.

Here’s the script:


#!/usr/bin/env python3

import re
import urllib.request
import os
import sys
import smtplib

# -----------------------------------------------------------------------------
# variable: lastIPAddressFileName
#
# The name of a file used to store the last committed IP address.
# -----------------------------------------------------------------------------
lastIPAddressFileName = '/var/tmp/last_router_ip'

# -----------------------------------------------------------------------------
# function: mailInfo
#
# Emails a message to one or more recipients. This should just be used for
# errors and successful changes of IP. (I.e. not used for regular no-change
# messages).
# -----------------------------------------------------------------------------
def mailInfo(info):
	#print('Sending email [{0}]'.format(info))
	from email.mime.text import MIMEText

	msg = MIMEText(
	"Hello from the Raspberry Pi DNS IP update utility.\n"
	"I have a new message for you:\n\n{0}".format(info))

	msg['Subject'] = 'CDS.RPi DSN IP Utility'
	msg['From'] = 'CDS RPi'
	msg['To'] = 'jon@carpediemsystems.co.uk'

	s = smtplib.SMTP('smtp.gmail.com', 587)
	s.ehlo()
	s.starttls()
	s.ehlo()
	s.login('jon@carpediemsystems.co.uk', '******')
	s.sendmail('jon@carpediemsystems.co.uk', ['jon@carpediemsystems.co.uk'], msg.as_string())
	s.quit()

# -----------------------------------------------------------------------------
# function: findRouterIP
#
# Returns the IP of the router by scraping the results of the DYN DNS check
# IP web site. None is returned if the address cannot be found.
# -----------------------------------------------------------------------------
def findRouterIP():

	ipAddress = None
	webAddress = 'http://checkip.dyndns.org/'
	siteData = urllib.request.urlopen(url = webAddress).read()
	siteAsText = str(siteData)
	ipAddresses = re.findall('\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', siteAsText)

	if(len(ipAddresses) == 1):
		ipAddress = ipAddresses[0]

	return ipAddress

# -----------------------------------------------------------------------------
# function: dynDnsPerformUpdate
#
# Updates the IP address of a DynDNS host using Dyn's Perform Update request.
# The resulting web page is scraped and the function will return True if one
# of the valid results codes is seen (meaning the IP address was either updated
# or was not changed).
# -----------------------------------------------------------------------------
def dynDnsPerformUpdate(username, password, hostname, ipAddress):

	url = 'http://{0}:{1}@members.dyndns.org/nic/update?hostname={2}&myip={3}&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG'.format(username, password, hostname, ipAddress)
	fancyOpener = urllib.request.FancyURLopener()
	siteData = fancyOpener.open(url).read()
	siteAsText = str(siteData)

	acceptableResults = ['good', 'nochg']
	isOk = len([r for r in acceptableResults if r in siteAsText]) > 0
	return isOk

# -----------------------------------------------------------------------------
# function: checkForNewIPAddress
#
# Finds the current router IP address and compares it to the last address
# saved to the lastIPAddressFileName file (if it exists).
#
# Returns a tuple:
#	Current IP address (string)
#	Is this a new and valid IP address (boolean)
# -----------------------------------------------------------------------------
def checkForNewIPAddress():
	lastIPAddress = None

	if(os.path.exists(lastIPAddressFileName) == True):
		with open(lastIPAddressFileName, 'r') as f:
			lastIPAddress = f.read()

	currentIPAddress = findRouterIP()
	isNewIPAddress = ((currentIPAddress != None) and (currentIPAddress != lastIPAddress))

	return currentIPAddress, isNewIPAddress

# -----------------------------------------------------------------------------
# function: saveNewIPAddress
#
# Sends the new IP address to Dyn DNS. This is also saved to the
# lastIPAddressFileName file if the web operation succeeded.
#
# Returns True if the operation succeeded.
# -----------------------------------------------------------------------------
def saveNewIPAddress(newIPAddress):
	username = '******'
	password = '******'
	host = '******.dnsalias.net'
	didUpdate = dynDnsPerformUpdate(username = username, password = password, hostname = host, ipAddress = newIPAddress)

	if(didUpdate == True):
		with open(lastIPAddressFileName, 'w') as f:
			f.write(newIPAddress)

	return didUpdate

# -----------------------------------------------------------------------------
# Script main
# -----------------------------------------------------------------------------

try:
	currentIPAddress, isNewIPAddress = checkForNewIPAddress()

	if(currentIPAddress == None):
		mailInfo('ERROR: failed to get current IP address')
	elif(isNewIPAddress == False):
		print('IP address is unchanged - nothing to do')
	else:
		saveNewIPAddress(currentIPAddress)
		mailInfo('IP address updated to {0}'.format(currentIPAddress))
except:
   mailInfo("ERROR: An unexpected error was detected in the script:", sys.exc_info()[0])

Note: the scripting presentation in this post was done by using WordPress.com’s sourcecode widget, as described here.

The script was saved to:

 /bin/DNS_IP_Util.py

and made executable with:

sudo chmod +x DNS_IP_Util.py

It was scheduled to run hourly by editing root’s cron file. The command to run the editor is:

sudo crontab -e

The cron file was edited to include the following two lines:

# DNS update, every day, hourly
0 * * * * /bin/DNS_IP_Util.py &
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s