Dynamic DNS Updates via the Rackspace Cloud DNS

Do you remember the old days when dyndns.org offered free sub domains, that pointed to your home internet connection? This service allowed you to access your home computer remotely, by hostname, without the need of remembering your IP Address.

I wrote a python script that does something similar. It uses the Rackspace Cloud API to manage an DNS A record of a domain that you own and is hosted with Rackspace.

Below is the code:

#!/usr/bin/env python
import pyrax, urllib, socket

'''Variables'''
username = '{USERNAME}'
api_key = '{API_KEY}'
domain_name = '{DOMAIN.COM}'
record_id = '{RECORD_ID}'
dyn_domain = '{DYNDNS_HOSTNAME}'
debug = True

'''Do not change these variables'''
ip = ''
domain = ''
record = ''

'''Functions'''
def get_ip():
	global ip, debug
	ip = urllib.urlopen('http://icanhazip.com')
	ip = ip.read()
	ip = ip.strip("\n")
	
	if debug:
		print "*** Current IP Address: %s" % ip
	
	return ip
	
def get_dyn_ip():
	global ip, debug, dyn_domain
	dyn_domain_ip = socket.gethostbyname(dyn_domain)
	
	if debug:
		print "*** Current IP Address: %s" % ip
		print "*** DNS A Record of %s: %s" % (dyn_domain, dyn_domain_ip)
	
	if ip == dyn_domain_ip:
		if debug:
			print "*** %s is already current for %s. Nothing to do." % (dyn_domain_ip, dyn_domain)
		exit(1)
	else:
		update_domain(ip)
	
	return ip

def rax_auth(user, api):
	global debug
	
	if debug:
		print "*** Username: %s, API_Key: %s" % (user, api)

	pyrax.set_setting('identity_type', 'rackspace')
	pyrax.set_credentials(user, api)

def get_domain(dns_domain, dns_record_id):
	global debug, domain, record

	if debug:
		print "*** Domain Name: %s, Record Id: %s" % (dns_domain, dns_record_id)

	domain = pyrax.cloud_dns.find(name=dns_domain)
	record = domain.get_record(dns_record_id)
	
	return domain, record

def update_domain(ip):
	global debug, username, api_key, domain_name, record_id
	
	rax_auth(username, api_key)
	get_domain(domain_name, record_id)
	if debug:
		print "*** Updated IP Address: %s" % ip
	record.update(data=ip)

'''Executing Functions'''
get_ip()
get_dyn_ip()

There are a couple things that still need to be added:

Given that, the current limitations are:

In the examples, I’m going to be using CentOS 7. The first thing that I’m going to do is install EPEL, which will allow me to install the package ‘pip’.

[jtdub@pyrax-test ~]$ sudo rpm -ivh http://dl.fedoraproject.org/pub/epel/beta/7/x86_64/epel-release-7-0.2.noarch.rpm
Retrieving http://dl.fedoraproject.org/pub/epel/beta/7/x86_64/epel-release-7-0.2.noarch.rpm
warning: /var/tmp/rpm-tmp.tb0sU3: Header V3 RSA/SHA256 Signature, key ID 352c64e5: NOKEY
Preparing...                          ################################# [100%]
Updating / installing...
   1:epel-release-7-0.2               ################################# [100%]
[jtdub@pyrax-test ~]$ sudo yum -y install python-pip
Loaded plugins: fastestmirror
epel/x86_64/metalink                                                                                                                                              |  11 kB  00:00:00     
epel                                                                                                                                                              | 3.7 kB  00:00:00     
(1/2): epel/x86_64/group_gz                                                                                                                                       | 163 kB  00:00:00     
(2/2): epel/x86_64/primary_db                                                                                                                                     | 2.1 MB  00:00:00     
Loading mirror speeds from cached hostfile
 * base: centos.someimage.com
 * epel: mirrors.mit.edu
 * extras: mirrors.einstein.yu.edu
 * updates: centos.hostingxtreme.com
Resolving Dependencies
--> Running transaction check
---> Package python-pip.noarch 0:1.3.1-4.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

=========================================================================================================================================================================================
 Package                                       Arch                                      Version                                           Repository                               Size
=========================================================================================================================================================================================
Installing:
 python-pip                                    noarch                                    1.3.1-4.el7                                       epel                                    315 k

Transaction Summary
=========================================================================================================================================================================================
Install  1 Package

Total download size: 315 k
Installed size: 1.0 M
Downloading packages:
python-pip-1.3.1-4.el7.noarch.rpm                                                                                                                                 | 315 kB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Warning: RPMDB altered outside of yum.
  Installing : python-pip-1.3.1-4.el7.noarch                                                                                                                                         1/1 
  Verifying  : python-pip-1.3.1-4.el7.noarch                                                                                                                                         1/1 

Installed:
  python-pip.noarch 0:1.3.1-4.el7                                                                                                                                                        

Complete!
[jtdub@pyrax-test ~]$

Once that is installed, we can install the python module that we’ll need, which is pyrax - which is the Rackspace Python SDK for their Cloud.

[jtdub@pyrax-test ~]$ sudo pip install pyrax
Downloading/unpacking pyrax
  Downloading pyrax-1.9.0.tar.gz (308kB): 308kB downloaded
  Running setup.py egg_info for package pyrax
    
Downloading/unpacking python-novaclient>=2.13.0 (from pyrax)
  Downloading python-novaclient-2.18.1.tar.gz (254kB): 254kB downloaded
  Running setup.py egg_info for package python-novaclient
    
    Installed /tmp/pip-build-root/python-novaclient/pbr-0.10.0-py2.7.egg
    [pbr] Processing SOURCES.txt
    warning: LocalManifestMaker: standard file '-c' not found
    
    warning: no previously-included files found matching '.gitignore'
    warning: no previously-included files found matching '.gitreview'
    warning: no previously-included files matching '*.pyc' found anywhere in distribution
    warning: no previously-included files found matching '.gitignore'
    warning: no previously-included files found matching '.gitreview'
Downloading/unpacking rackspace-novaclient (from pyrax)
  Downloading rackspace-novaclient-1.4.tar.gz
  Running setup.py egg_info for package rackspace-novaclient
    
Downloading/unpacking keyring (from pyrax)
  Downloading keyring-3.8.zip (84kB): 84kB downloaded
  Running setup.py egg_info for package keyring
    
    warning: no previously-included files found matching '.hg/last-message.txt'
Downloading/unpacking requests>=2.2.1 (from pyrax)
  Downloading requests-2.3.0.tar.gz (429kB): 429kB downloaded
  Running setup.py egg_info for package requests
    
Downloading/unpacking six>=1.5.2 (from pyrax)
  Downloading six-1.7.3.tar.gz
  Running setup.py egg_info for package six
    
    no previously-included directories found matching 'documentation/_build'
Downloading/unpacking mock (from pyrax)
  Downloading mock-1.0.1.tar.gz (818kB): 818kB downloaded
  Running setup.py egg_info for package mock
    
    warning: no files found matching '*.png' under directory 'docs'
    warning: no files found matching '*.css' under directory 'docs'
    warning: no files found matching '*.html' under directory 'docs'
    warning: no files found matching '*.js' under directory 'docs'
Downloading/unpacking pbr>=0.6,!=0.7,<1.0 (from python-novaclient>=2.13.0->pyrax)
  Downloading pbr-0.10.0.tar.gz (77kB): 77kB downloaded
  Running setup.py egg_info for package pbr
    [pbr] Processing SOURCES.txt
    warning: LocalManifestMaker: standard file '-c' not found
    
    warning: no previously-included files found matching '.gitignore'
    warning: no previously-included files found matching '.gitreview'
    warning: no previously-included files matching '*.pyc' found anywhere in distribution
    warning: no previously-included files found matching '.gitignore'
    warning: no previously-included files found matching '.gitreview'
    warning: no previously-included files matching '*.pyc' found anywhere in distribution
Downloading/unpacking argparse (from python-novaclient>=2.13.0->pyrax)
  Downloading argparse-1.2.1.tar.gz (69kB): 69kB downloaded
  Running setup.py egg_info for package argparse
    
    warning: no previously-included files matching '*.pyc' found anywhere in distribution
    warning: no previously-included files matching '*.pyo' found anywhere in distribution
    warning: no previously-included files matching '*.orig' found anywhere in distribution
    warning: no previously-included files matching '*.rej' found anywhere in distribution
    no previously-included directories found matching 'doc/_build'
    no previously-included directories found matching 'env24'
    no previously-included directories found matching 'env25'
    no previously-included directories found matching 'env26'
    no previously-included directories found matching 'env27'
Downloading/unpacking iso8601>=0.1.9 (from python-novaclient>=2.13.0->pyrax)
  Downloading iso8601-0.1.10.tar.gz
  Running setup.py egg_info for package iso8601
    
Downloading/unpacking PrettyTable>=0.7,<0.8 (from python-novaclient>=2.13.0->pyrax)
  Downloading prettytable-0.7.2.tar.bz2
  Running setup.py egg_info for package PrettyTable
    
Downloading/unpacking simplejson>=2.0.9 (from python-novaclient>=2.13.0->pyrax)
  Downloading simplejson-3.6.0.tar.gz (70kB): 70kB downloaded
  Running setup.py egg_info for package simplejson
    
Downloading/unpacking Babel>=1.3 (from python-novaclient>=2.13.0->pyrax)
  Downloading Babel-1.3.tar.gz (3.4MB): 3.4MB downloaded
  Running setup.py egg_info for package Babel
    
    warning: no previously-included files matching '*' found under directory 'docs/_build'
    warning: no previously-included files matching '*.pyc' found under directory 'tests'
    warning: no previously-included files matching '*.pyo' found under directory 'tests'
Downloading/unpacking rackspace-auth-openstack (from rackspace-novaclient->pyrax)
  Downloading rackspace-auth-openstack-1.3.tar.gz
  Running setup.py egg_info for package rackspace-auth-openstack
    
Downloading/unpacking os-diskconfig-python-novaclient-ext (from rackspace-novaclient->pyrax)
  Downloading os_diskconfig_python_novaclient_ext-0.1.2.tar.gz
  Running setup.py egg_info for package os-diskconfig-python-novaclient-ext
    
Downloading/unpacking rax-scheduled-images-python-novaclient-ext (from rackspace-novaclient->pyrax)
  Downloading rax_scheduled_images_python_novaclient_ext-0.2.2.tar.gz
  Running setup.py egg_info for package rax-scheduled-images-python-novaclient-ext
    
Downloading/unpacking os-networksv2-python-novaclient-ext (from rackspace-novaclient->pyrax)
  Downloading os_networksv2_python_novaclient_ext-0.21.tar.gz
  Running setup.py egg_info for package os-networksv2-python-novaclient-ext
    
Downloading/unpacking os-virtual-interfacesv2-python-novaclient-ext (from rackspace-novaclient->pyrax)
  Downloading os_virtual_interfacesv2_python_novaclient_ext-0.15.tar.gz
  Running setup.py egg_info for package os-virtual-interfacesv2-python-novaclient-ext
    
Downloading/unpacking rax-default-network-flags-python-novaclient-ext (from rackspace-novaclient->pyrax)
  Downloading rax_default_network_flags_python_novaclient_ext-0.2.4.tar.gz
  Running setup.py egg_info for package rax-default-network-flags-python-novaclient-ext
    
Requirement already satisfied (use --upgrade to upgrade): pip in /usr/lib/python2.7/site-packages (from pbr>=0.6,!=0.7,<1.0->python-novaclient>=2.13.0->pyrax)
Downloading/unpacking pytz>=0a (from Babel>=1.3->python-novaclient>=2.13.0->pyrax)
  Downloading pytz-2014.4.tar.bz2 (159kB): 159kB downloaded
  Running setup.py egg_info for package pytz
    
Installing collected packages: pyrax, python-novaclient, rackspace-novaclient, keyring, requests, six, mock, pbr, argparse, iso8601, PrettyTable, simplejson, Babel, rackspace-auth-openstack, os-diskconfig-python-novaclient-ext, rax-scheduled-images-python-novaclient-ext, os-networksv2-python-novaclient-ext, os-virtual-interfacesv2-python-novaclient-ext, rax-default-network-flags-python-novaclient-ext, pytz
  Running setup.py install for pyrax
    /usr/bin/python -O /tmp/tmpB0aWjs.py
    removing /tmp/tmpB0aWjs.py
    
  Running setup.py install for python-novaclient
    [pbr] Reusing existing SOURCES.txt
    Installing nova script to /usr/bin
  Running setup.py install for rackspace-novaclient
    
  Running setup.py install for keyring
    
    warning: no previously-included files found matching '.hg/last-message.txt'
    Installing keyring script to /usr/bin
  Running setup.py install for requests
    
  Running setup.py install for six
    
    no previously-included directories found matching 'documentation/_build'
  Running setup.py install for mock
    
    warning: no files found matching '*.png' under directory 'docs'
    warning: no files found matching '*.css' under directory 'docs'
    warning: no files found matching '*.html' under directory 'docs'
    warning: no files found matching '*.js' under directory 'docs'
  Running setup.py install for pbr
    [pbr] Reusing existing SOURCES.txt
  Running setup.py install for argparse
    
    warning: no previously-included files matching '*.pyc' found anywhere in distribution
    warning: no previously-included files matching '*.pyo' found anywhere in distribution
    warning: no previously-included files matching '*.orig' found anywhere in distribution
    warning: no previously-included files matching '*.rej' found anywhere in distribution
    no previously-included directories found matching 'doc/_build'
    no previously-included directories found matching 'env24'
    no previously-included directories found matching 'env25'
    no previously-included directories found matching 'env26'
    no previously-included directories found matching 'env27'
  Running setup.py install for iso8601
    
  Running setup.py install for PrettyTable
    
  Running setup.py install for simplejson
    building 'simplejson._speedups' extension
    gcc -pthread -fno-strict-aliasing -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -I/usr/include/python2.7 -c simplejson/_speedups.c -o build/temp.linux-x86_64-2.7/simplejson/_speedups.o
    unable to execute gcc: No such file or directory
    ***************************************************************************
    WARNING: The C extension could not be compiled, speedups are not enabled.
    Failure information, if any, is above.
    I'm retrying the build without the C extension now.
    ***************************************************************************
    
    ***************************************************************************
    WARNING: The C extension could not be compiled, speedups are not enabled.
    Plain-Python installation succeeded.
    ***************************************************************************
  Running setup.py install for Babel
    
    warning: no previously-included files matching '*' found under directory 'docs/_build'
    warning: no previously-included files matching '*.pyc' found under directory 'tests'
    warning: no previously-included files matching '*.pyo' found under directory 'tests'
    Installing pybabel script to /usr/bin
  Running setup.py install for rackspace-auth-openstack
    
  Running setup.py install for os-diskconfig-python-novaclient-ext
    
  Running setup.py install for rax-scheduled-images-python-novaclient-ext
    
  Running setup.py install for os-networksv2-python-novaclient-ext
    
  Running setup.py install for os-virtual-interfacesv2-python-novaclient-ext
    
  Running setup.py install for rax-default-network-flags-python-novaclient-ext
    
  Running setup.py install for pytz
    
Successfully installed pyrax python-novaclient rackspace-novaclient keyring requests six mock pbr argparse iso8601 PrettyTable simplejson Babel rackspace-auth-openstack os-diskconfig-python-novaclient-ext rax-scheduled-images-python-novaclient-ext os-networksv2-python-novaclient-ext os-virtual-interfacesv2-python-novaclient-ext rax-default-network-flags-python-novaclient-ext pytz
Cleaning up...

Once you’ve installed the needed modules, you can verify that they are available for use.

[jtdub@pyrax-test ~]$ python
Python 2.7.5 (default, Jun 17 2014, 18:11:42) 
[GCC 4.8.2 20140120 (Red Hat 4.8.2-16)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyrax
>>> dir(pyrax)
['AutoScaleClient', 'CloudBlockStorageClient', 'CloudDNSClient', 'CloudDatabaseClient', 'CloudLoadBalancerClient', 'CloudMonitorClient', 'CloudNetworkClient', 'CloudServer', 'ConfigParser', 'ImageClient', 'QueueClient', 'Settings', 'StorageClient', 'USER_AGENT', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', '_assure_identity', '_client_classes', '_create_client', '_create_identity', '_cs_auth_plugin', '_cs_client', '_cs_exceptions', '_cs_shell', '_environment', '_get_service_endpoint', '_http_debug', '_id_type', '_import_identity', '_logger', '_make_agent_name', '_require_auth', '_safe_region', 'absolute_import', 'auth_with_token', 'authenticate', 'autoscale', 'base_identity', 'clear_credentials', 'client', 'client_class_for_service', 'cloud_blockstorage', 'cloud_databases', 'cloud_dns', 'cloud_loadbalancers', 'cloud_monitoring', 'cloud_networks', 'cloudblockstorage', 'clouddatabases', 'clouddns', 'cloudfiles', 'cloudloadbalancers', 'cloudmonitoring', 'cloudnetworks', 'cloudservers', 'config_file', 'connect_to_autoscale', 'connect_to_cloud_blockstorage', 'connect_to_cloud_databases', 'connect_to_cloud_dns', 'connect_to_cloud_loadbalancers', 'connect_to_cloud_monitoring', 'connect_to_cloud_networks', 'connect_to_cloudfiles', 'connect_to_cloudservers', 'connect_to_images', 'connect_to_queues', 'connect_to_services', 'create_context', 'default_encoding', 'default_region', 'exc', 'exceptions', 'get_encoding', 'get_environment', 'get_http_debug', 'get_setting', 'http', 'identity', 'image', 'images', 'inspect', 'keyring', 'keyring_auth', 'keystone_identity', 'list_environments', 'logging', 'manager', 'object_storage', 'os', 'queueing', 'queues', 'rax_identity', 're', 'regions', 'resource', 'services', 'set_credential_file', 'set_credentials', 'set_default_region', 'set_environment', 'set_http_debug', 'set_setting', 'settings', 'utils', 'version', 'warnings', 'wraps']
>>> import urllib
>>> dir(urllib)
['ContentTooShortError', 'FancyURLopener', 'MAXFTPCACHE', 'URLopener', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__version__', '_asciire', '_ftperrors', '_have_ssl', '_hexdig', '_hextochr', '_hostprog', '_is_unicode', '_localhost', '_noheaders', '_nportprog', '_passwdprog', '_portprog', '_queryprog', '_safe_map', '_safe_quoters', '_tagprog', '_thishost', '_typeprog', '_urlopener', '_userprog', '_valueprog', 'addbase', 'addclosehook', 'addinfo', 'addinfourl', 'always_safe', 'base64', 'basejoin', 'c', 'ftpcache', 'ftperrors', 'ftpwrapper', 'getproxies', 'getproxies_environment', 'i', 'localhost', 'noheaders', 'os', 'pathname2url', 'proxy_bypass', 'proxy_bypass_environment', 'quote', 'quote_plus', 're', 'reporthook', 'socket', 'splitattr', 'splithost', 'splitnport', 'splitpasswd', 'splitport', 'splitquery', 'splittag', 'splittype', 'splituser', 'splitvalue', 'ssl', 'string', 'sys', 'test1', 'thishost', 'time', 'toBytes', 'unquote', 'unquote_plus', 'unwrap', 'url2pathname', 'urlcleanup', 'urlencode', 'urlopen', 'urlretrieve']
>>> import socket
>>> dir(socket)
['AF_APPLETALK', 'AF_ASH', 'AF_ATMPVC', 'AF_ATMSVC', 'AF_AX25', 'AF_BLUETOOTH', 'AF_BRIDGE', 'AF_DECnet', 'AF_ECONET', 'AF_INET', 'AF_INET6', 'AF_IPX', 'AF_IRDA', 'AF_KEY', 'AF_LLC', 'AF_NETBEUI', 'AF_NETLINK', 'AF_NETROM', 'AF_PACKET', 'AF_PPPOX', 'AF_ROSE', 'AF_ROUTE', 'AF_SECURITY', 'AF_SNA', 'AF_TIPC', 'AF_UNIX', 'AF_UNSPEC', 'AF_WANPIPE', 'AF_X25', 'AI_ADDRCONFIG', 'AI_ALL', 'AI_CANONNAME', 'AI_NUMERICHOST', 'AI_NUMERICSERV', 'AI_PASSIVE', 'AI_V4MAPPED', 'BDADDR_ANY', 'BDADDR_LOCAL', 'BTPROTO_HCI', 'BTPROTO_L2CAP', 'BTPROTO_RFCOMM', 'BTPROTO_SCO', 'CAPI', 'EAI_ADDRFAMILY', 'EAI_AGAIN', 'EAI_BADFLAGS', 'EAI_FAIL', 'EAI_FAMILY', 'EAI_MEMORY', 'EAI_NODATA', 'EAI_NONAME', 'EAI_OVERFLOW', 'EAI_SERVICE', 'EAI_SOCKTYPE', 'EAI_SYSTEM', 'EBADF', 'EINTR', 'HCI_DATA_DIR', 'HCI_FILTER', 'HCI_TIME_STAMP', 'INADDR_ALLHOSTS_GROUP', 'INADDR_ANY', 'INADDR_BROADCAST', 'INADDR_LOOPBACK', 'INADDR_MAX_LOCAL_GROUP', 'INADDR_NONE', 'INADDR_UNSPEC_GROUP', 'IPPORT_RESERVED', 'IPPORT_USERRESERVED', 'IPPROTO_AH', 'IPPROTO_DSTOPTS', 'IPPROTO_EGP', 'IPPROTO_ESP', 'IPPROTO_FRAGMENT', 'IPPROTO_GRE', 'IPPROTO_HOPOPTS', 'IPPROTO_ICMP', 'IPPROTO_ICMPV6', 'IPPROTO_IDP', 'IPPROTO_IGMP', 'IPPROTO_IP', 'IPPROTO_IPIP', 'IPPROTO_IPV6', 'IPPROTO_NONE', 'IPPROTO_PIM', 'IPPROTO_PUP', 'IPPROTO_RAW', 'IPPROTO_ROUTING', 'IPPROTO_RSVP', 'IPPROTO_TCP', 'IPPROTO_TP', 'IPPROTO_UDP', 'IPV6_CHECKSUM', 'IPV6_DSTOPTS', 'IPV6_HOPLIMIT', 'IPV6_HOPOPTS', 'IPV6_JOIN_GROUP', 'IPV6_LEAVE_GROUP', 'IPV6_MULTICAST_HOPS', 'IPV6_MULTICAST_IF', 'IPV6_MULTICAST_LOOP', 'IPV6_NEXTHOP', 'IPV6_PKTINFO', 'IPV6_RECVDSTOPTS', 'IPV6_RECVHOPLIMIT', 'IPV6_RECVHOPOPTS', 'IPV6_RECVPKTINFO', 'IPV6_RECVRTHDR', 'IPV6_RECVTCLASS', 'IPV6_RTHDR', 'IPV6_RTHDRDSTOPTS', 'IPV6_RTHDR_TYPE_0', 'IPV6_TCLASS', 'IPV6_UNICAST_HOPS', 'IPV6_V6ONLY', 'IP_ADD_MEMBERSHIP', 'IP_DEFAULT_MULTICAST_LOOP', 'IP_DEFAULT_MULTICAST_TTL', 'IP_DROP_MEMBERSHIP', 'IP_HDRINCL', 'IP_MAX_MEMBERSHIPS', 'IP_MULTICAST_IF', 'IP_MULTICAST_LOOP', 'IP_MULTICAST_TTL', 'IP_OPTIONS', 'IP_RECVOPTS', 'IP_RECVRETOPTS', 'IP_RETOPTS', 'IP_TOS', 'IP_TTL', 'MSG_CTRUNC', 'MSG_DONTROUTE', 'MSG_DONTWAIT', 'MSG_EOR', 'MSG_OOB', 'MSG_PEEK', 'MSG_TRUNC', 'MSG_WAITALL', 'MethodType', 'NETLINK_DNRTMSG', 'NETLINK_FIREWALL', 'NETLINK_IP6_FW', 'NETLINK_NFLOG', 'NETLINK_ROUTE', 'NETLINK_USERSOCK', 'NETLINK_XFRM', 'NI_DGRAM', 'NI_MAXHOST', 'NI_MAXSERV', 'NI_NAMEREQD', 'NI_NOFQDN', 'NI_NUMERICHOST', 'NI_NUMERICSERV', 'PACKET_BROADCAST', 'PACKET_FASTROUTE', 'PACKET_HOST', 'PACKET_LOOPBACK', 'PACKET_MULTICAST', 'PACKET_OTHERHOST', 'PACKET_OUTGOING', 'PF_PACKET', 'RAND_add', 'RAND_egd', 'RAND_status', 'SHUT_RD', 'SHUT_RDWR', 'SHUT_WR', 'SOCK_DGRAM', 'SOCK_RAW', 'SOCK_RDM', 'SOCK_SEQPACKET', 'SOCK_STREAM', 'SOL_HCI', 'SOL_IP', 'SOL_SOCKET', 'SOL_TCP', 'SOL_TIPC', 'SOL_UDP', 'SOMAXCONN', 'SO_ACCEPTCONN', 'SO_ATTACH_FILTER', 'SO_BINDTODEVICE', 'SO_BROADCAST', 'SO_BSDCOMPAT', 'SO_DEBUG', 'SO_DETACH_FILTER', 'SO_DONTROUTE', 'SO_ERROR', 'SO_KEEPALIVE', 'SO_LINGER', 'SO_NO_CHECK', 'SO_OOBINLINE', 'SO_PASSCRED', 'SO_PASSSEC', 'SO_PEERCRED', 'SO_PEERNAME', 'SO_PEERSEC', 'SO_PRIORITY', 'SO_RCVBUF', 'SO_RCVBUFFORCE', 'SO_RCVLOWAT', 'SO_RCVTIMEO', 'SO_REUSEADDR', 'SO_REUSEPORT', 'SO_SECURITY_AUTHENTICATION', 'SO_SECURITY_ENCRYPTION_NETWORK', 'SO_SECURITY_ENCRYPTION_TRANSPORT', 'SO_SNDBUF', 'SO_SNDBUFFORCE', 'SO_SNDLOWAT', 'SO_SNDTIMEO', 'SO_TIMESTAMP', 'SO_TIMESTAMPNS', 'SO_TYPE', 'SSL_ERROR_EOF', 'SSL_ERROR_INVALID_ERROR_CODE', 'SSL_ERROR_SSL', 'SSL_ERROR_SYSCALL', 'SSL_ERROR_WANT_CONNECT', 'SSL_ERROR_WANT_READ', 'SSL_ERROR_WANT_WRITE', 'SSL_ERROR_WANT_X509_LOOKUP', 'SSL_ERROR_ZERO_RETURN', 'SocketType', 'StringIO', 'TCP_CONGESTION', 'TCP_CORK', 'TCP_DEFER_ACCEPT', 'TCP_INFO', 'TCP_KEEPCNT', 'TCP_KEEPIDLE', 'TCP_KEEPINTVL', 'TCP_LINGER2', 'TCP_MAXSEG', 'TCP_MD5SIG', 'TCP_MD5SIG_MAXKEYLEN', 'TCP_NODELAY', 'TCP_QUICKACK', 'TCP_SYNCNT', 'TCP_WINDOW_CLAMP', 'TIPC_ADDR_ID', 'TIPC_ADDR_NAME', 'TIPC_ADDR_NAMESEQ', 'TIPC_CFG_SRV', 'TIPC_CLUSTER_SCOPE', 'TIPC_CONN_TIMEOUT', 'TIPC_CRITICAL_IMPORTANCE', 'TIPC_DEST_DROPPABLE', 'TIPC_HIGH_IMPORTANCE', 'TIPC_IMPORTANCE', 'TIPC_LOW_IMPORTANCE', 'TIPC_MEDIUM_IMPORTANCE', 'TIPC_NODE_SCOPE', 'TIPC_PUBLISHED', 'TIPC_SRC_DROPPABLE', 'TIPC_SUBSCR_TIMEOUT', 'TIPC_SUB_CANCEL', 'TIPC_SUB_PORTS', 'TIPC_SUB_SERVICE', 'TIPC_TOP_SRV', 'TIPC_WAIT_FOREVER', 'TIPC_WITHDRAWN', 'TIPC_ZONE_SCOPE', '_GLOBAL_DEFAULT_TIMEOUT', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '_closedsocket', '_delegate_methods', '_fileobject', '_m', '_realsocket', '_socket', '_socketmethods', '_socketobject', '_ssl', 'create_connection', 'errno', 'error', 'fromfd', 'gaierror', 'getaddrinfo', 'getdefaulttimeout', 'getfqdn', 'gethostbyaddr', 'gethostbyname', 'gethostbyname_ex', 'gethostname', 'getnameinfo', 'getprotobyname', 'getservbyname', 'getservbyport', 'has_ipv6', 'herror', 'htonl', 'htons', 'inet_aton', 'inet_ntoa', 'inet_ntop', 'inet_pton', 'm', 'meth', 'ntohl', 'ntohs', 'os', 'p', 'partial', 'setdefaulttimeout', 'socket', 'socketpair', 'ssl', 'sslerror', 'sys', 'timeout', 'warnings']

In my example, I’m going to use the hostname of myhomepc.jtdub.com, which points to 192.168.1.1. While still in the python prompt, I’m going to call the pyrax module to get the {record_id}, which in this example output is id: ‘A-2222222’.

>>> import pyrax
>>> pyrax.set_setting('identity_type', 'rackspace')
>>> pyrax.set_credentials('{username}', '{api_key}')
>>> domain = pyrax.cloud_dns.find(name="jtdub.com")
>>> record = domain.get_record('')
>>> print record
<CloudDNSRecord domain_id=0000000, records=[{u'updated': u'2013-07-31T06:17:35.000+0000', u'name': u'www.jtdub.com', u'created': u'2012-06-07T23:56:27.000+0000', u'type': u'A', u'ttl': 300, u'data': u'174.143.185.156', u'id': u'A-0000000'}, {u'updated': u'2013-07-31T06:17:44.000+0000', u'name': u'jtdub.com', u'created': u'2012-06-07T23:58:31.000+0000', u'type': u'A', u'ttl': 300, u'data': u'174.143.185.156', u'id': u'A-1111111'}, {u'updated': u'2014-07-26T21:37:24.000+0000', u'name': u'myhomepc.jtdub.com', u'created': u'2014-07-26T21:37:24.000+0000', u'type': u'A', u'ttl': 300, u'data': u'192.168.1.1', u'id': u'A-2222222'},}], totalEntries=13>
>>>
>>> record = domain.get_record('A-2222222')
>>> print record
<CloudDNSRecord data=192.168.1.1, domain_id=0000000, id=A-2222222, name=myhomepc.jtdub.com, ttl=300, type=A>

Replace {username} and {api_key} with your rackspace cloud account username and api key. Now that we have the necessary information, we can edit the variables of the script and run it!

[jtdub@pyrax-test ~]$ git clone https://github.com/jtdub/pyraxDynDNS.git
Cloning into 'pyraxDynDNS'...
remote: Counting objects: 13, done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 13 (delta 3), reused 6 (delta 1)
Unpacking objects: 100% (13/13), done.
[jtdub@pyrax-test ~]$ cd pyraxDynDNS/
[jtdub@pyrax-test pyraxDynDNS]$ vim pyraxDynDNS.py
[jtdub@pyrax-test pyraxDynDNS]$ chmod +x pyraxDynDNS.py

Here’s my first run of the script. Here you can see that it determines my public IP Address, then determines the DNS A record of my {dyn_domain} hostname. As they are different, it authenticates to the API and makes a call to update the DNS A record of myhomepc.jtdub.com with my current public IP Address.

[jtdub@pyrax-test pyraxDynDNS]$ ./pyraxDynDNS.py
*** Current IP Address: 240.0.0.1
*** Current IP Address: 240.0.0.1
*** DNS A Record of myhomepc.jtdub.com: 192.168.1.1
*** Username: {username}, API_Key: {api_key}
*** Domain Name: jtdub.com, Record Id: A-2222222
*** Updated IP Address: 240.0.0.1

I gave DNS a five minutes to update, then I ran the script again. This time, the script determined that my current public IP Address is the same as my DNS A record of myhomepc.jtdub.com, so it didn’t even attempt to authenticate to the API to make any changes.

[jtdub@pyrax-test pyraxDynDNS]$ ./pyraxDynDNS.py 
*** Current IP Address: 240.0.0.1
*** Current IP Address: 240.0.0.1
*** DNS A Record of myhomepc.jtdub.com: 240.0.0.1
*** 240.0.0.1 is already current for myhomepc.jtdub.com. Nothing to do.

Pretty nifty, right? From here, you can put the script into a cron job to run once a day, or however often you want. There you go! Custom dynamic DNS hostname, to access your home PC, using the Rackspace Cloud!

The script can be found on github.

Here is more information on the Rackspace SDK