Simon posted in: Developer

Do you have:

  • A dynamic IP address at home?
  • A domain name with DNS hosted by Memset?

Then you can use this script straight away with the Memset API to make a public IP address for your home Internet connection which will always be correct.

If you don't have a domain with Memset then you can buy one, or you can setup your domain to use our name servers.

Setting up the DNS manager

Firstly make sure your domain is being served by the DNS manager. For the purpose of this we'll assume that it is called example.com

Now edit the records for your domain and add a new one with the name home (you can choose something different if you want). Create an A record and make the "Point to Address" be a valid IP address. Start with the IP address you see at this site which is your current IP address. You should make it have the lowest possible TTL (time to live) or 5 minutes so that changes will be reflected in the DNS as quickly as possible.

You'll have then made an A record pointing to home.example.com which will point to your current external home IP address.

Setting up the API key

Now you need to make an API key for the script. This is like a specific password for the script which you can revoke at any time through the control panel.

First go to manage your API keys.

Go to the "add a new key" section and make sure the key has the following scopes:

  • method:dns.zone_domain_list
  • method:dns.zone_info
  • method:dns.zone_record_update
  • method:dns.reload
  • method:job.status

Scoping the key gives it just enough power to do its job but no more.

Running the script

Download the zone_record_changer.py script from our Github repository.

Run it from your computer with:

./zone_record_changer.py YOUR_API_KEY_FROM_ABOVE home.example.com

and you should see output like this:

External IP is '10.0.0.1'
Domain 'example.com'
Old Zone Record {'address': '10.0.0.1',
 'id': '0123456789abcdef0123456789abcdef',
 'priority': 0,
 'record': 'home',
 'relative': True,
 'ttl': 300,
 'type': 'A',
 'zone_id': 'f0123456789abcdef0123456789abcde'}
New Zone Record {'address': '10.0.0.1',
 'id': '0123456789abcdef0123456789abcdef',
 'priority': 0,
 'record': 'home',
 'relative': True,
 'ttl': 300,
 'type': 'A',
 'zone_id': 'f0123456789abcdef0123456789abcde'}
Zone record unchanged, not reloading

or maybe like this:

External IP is '10.0.0.1'
Domain 'example.com'
Old Zone Record {'address': '10.0.0.2',
 'id': '0123456789abcdef0123456789abcdef',
 'priority': 0,
 'record': 'home',
 'relative': True,
 'ttl': 300,
 'type': 'A',
 'zone_id': 'f0123456789abcdef0123456789abcde'}
New Zone Record {'address': '10.0.0.1',
 'id': '0123456789abcdef0123456789abcdef',
 'priority': 0,
 'record': 'home',
 'relative': True,
 'ttl': 300,
 'type': 'A',
 'zone_id': 'f0123456789abcdef0123456789abcde'}
Reloading DNS . . . . . . . . . . . . OK

Within 5 minutes or so you should see that home.example.com has your new IP address.

Now you've got that working you need to make sure that this script runs whenever you connect to the Internet, which is unfortunately beyond the scope of this article. Here is a hint for Windows and here is a hint for Linux.

How the script works

I'm going to go through the script section by section in a top down fashion as it is a good example of how to use the Memset API.

Firstly the script is written in Python and uses the built in XML RPC support.

The uri variable defines the Memset API endpoint for XML RPC.

uri = "https://%s:x@api.memset.com/v1/xmlrpc"

import sys
from xmlrpclib import ServerProxy
from pprint import pformat
from urllib2 import urlopen
from time import sleep

This parses the arguments and uses the api.externalip.net service to get your current external IP.

It then creates the Memset API proxy with s = ServerProxy(uri %api_key) changes the IP in the zone and, if it has changed, reloads the DNS otherwise it ends.

def main():
    """
    Find the external IP and update the zone with it
    """
    if len(sys.argv) != 3:
        print >>sys.stderr, "Syntax: %s <api_key> <hostname>" % sys.argv[0]
        sys.exit(1)
    api_key, hostname = sys.argv[1:]
    external_ip = urlopen("http://api.externalip.net/ip/").read().strip()
    print "External IP is '%s'" % external_ip
    s = ServerProxy(uri % api_key)
    if change_ip(s, hostname, external_ip):
        reload_dns(s)
    else:
        print "Zone record unchanged, not reloading"

change_ip is where the bulk of the action happens.

First we use dns.zone_domain_list and run through the list of domains to find our domain, erroring if we don't find our domain example.com.

def change_ip(s, hostname, new_ip):
    """
    Find the zone record for domain and adjust the A record for
    hostname to new_ip

    Returns a boolean if the record changed
    """
    leafname, domain = hostname.split(".", 1)

    # Find the zone that the domain is in first
    zone_domains = s.dns.zone_domain_list()
    for zone_domain in zone_domains:
        if zone_domain['domain'] == domain:
            break
    else:
        raise ValueError("Couldn't find domain %r" % domain)
    zone_id = zone_domain['zone_id']
    print "Domain", pformat(domain)

Next we use dns.zone_info to read the zone and iterate through that until we find the specific record we are looking for (home.example.com).

 # List the zone to find the record to change
    zone = s.dns.zone_info(dict(id=zone_id))
    for record in zone['records']:
        if record['record'] == leafname and record['type'] == 'A':
            break
    else:
        raise ValueError("Couldn't find record for %r" % hostname)

Finally, we use dns.zone_record_update to actually change that zone record to the new IP address.

  # Change the zone record
    print "Old Zone Record", pformat(record)
    new_record = s.dns.zone_record_update(dict(id=record['id'], address=new_ip))
    print "New Zone Record", pformat(new_record)
    return record != new_record

If required we then reload the DNS, which pushes the database changes out to the DNS servers. We start the job using dns.reload and wait until it has finished, polling the status with job.status.

def reload_dns(s):
    """
    Reload the DNS servers
    """
    print "Reloading DNS",
    job = s.dns.reload()
    while not job['finished']:
        sys.stdout.flush()
        job = s.job.status(dict(id=job['id']))
        print ".",
        sleep(5)
    if not job['error']:
        print "OK"
    else:
        print "FAILED"

Conclusion

Hopefully you enjoyed that example of how to use the Memset API and it will inspire you to try it out and to make more cool stuff using it.