Custom Websites. Quality Communication.

Use AWS Route 53 and Ruby for Dynamic DNS

By on Dec 7, 2016 in Code | 0 comments

As home broadband becomes more common, more and more people have a high-bandwidth, unlimited connection with a changing IP at home. What if you want to always be able to connect to some device at your home? Or perhaps some other situation has arisen where a server is on a connection with an IP that changes, or may do so.

Dynamic DNS services can cost money so I looked around and found a way to create my own using Ruby and the aws-sdk for Ruby.

For this you need to be using AWS’ Route 53 Nameserver service.

Prerequisites:
Beyond the gems listed at the beginning of this code, you will need your AWS ID and Key to be active in your environment (otherwise you would need to add them in the requests below).

 



require 'aws-sdk'
require 'mechanize'
require "ipaddress"

# Creates a new resource record set for PUT to AWS
def put_resource_record_set(new_ip,hosted_zone_id,fqdn)
    upsert = make_change(new_ip,fqdn)

    put_resource_record_set = {
        :hosted_zone_id=> hosted_zone_id,
        :change_batch => {
            :changes => [
                upsert
            ],
        },
    }
end


# Creates a new UPSERT change for the resource record set
def make_change(new_ip,fqdn)
    change = {
        :action => 'UPSERT',
        :resource_record_set => {
            :name => fqdn,
            :type => "A",
            :ttl => 300,
            :resource_records => [{:value => new_ip}]
        }
    }

end



# Checks the IP of the host server against the specified AWS Route 53 record. 
# If it is different it sets the AWS record to the current server IP
def dyn_dns(hosted_zone_id, fqdn, aws_region)
    
    # Create a correctly formatted GET request to find the current IP address at DNS
    get_resource_record_set  = {
    hosted_zone_id: hosted_zone_id, 
    start_record_name: fqdn,
    max_items:1
    }

    mech = Mechanize.new
    # This is a simple service you can get your outgoing IP address from.
    page = mech.get "http://bot.whatismyipaddress.com"
    this_ip_string = page.body

    # Use the IPAddress library to validate the IP address string
    if IPAddress.valid? this_ip_string
        # Use the AWS sdk to connect to AWS, use the pre-made GET request to find the DNS IP  
        route53 = Aws::Route53::Client.new(region: aws_region)
        response = route53.list_resource_record_sets(get_resource_record_set)
        set_ip_string = response.resource_record_sets[0].resource_records[0].value
        unless set_ip_string == this_ip_string
            # The DNS IP address doesn't match the server address. We'll need to change it
            put_data = put_resource_record_set(this_ip_string, hosted_zone_id,fqdn)
            response = route53.change_resource_record_sets(put_data)
        end
    end     
end


Explanation

The function dyn_dns takes the following paramaters:

hosted_zone_id – the hosted zone ID of the Domain Name you want to manipulate. You get this from the Route 53 dashboard for the hosted zone.

fqdn – the fully qualified domain name you want to set the A record for.

aws_region – the aws_region your hosted_zone is in.

The function:

  1. Gets the current server IP.
  2. Uses the parameters to pull the IP for the fqdn from Route 53.
  3. Compares the two: if they are the same, it does nothing, otherwise;
  4. It “Upserts” (uploads, overriding if necessary) a new IP address into the Route 53 record for the fqdn

You could append a call to the dyn_dns function to the bottom of this script and then run it as a cron job every five minutes to keep a linux server Route 53 address current. I certainly have.