Introduction

Having internet access that doesn’t have a static IP address and a server that houses personal content makes ip address changes really annoying.  Granted Fios has a stickie IP address, meaning it doesn’t change very often, but with large power outages, it still tends to change.

The goal with this to not have to use a service like no-ip, where they manage an updated IP address for you with an application that lives on your machine. Instead, I monitor the IP address with a publicly available IP address tool, and any time the IP address is initially recorded or updated, I call a function within AWS Lambda to update the necessary addresses.

It’s worth noting that a pre-req for this project is that all of my DNS zones are hosted within AWS Route 53.

The Code

This project (like a lot of my projects) is using a SAM template. SAM templates are designed for resource creation within AWS. This way I can create a series of applications that work together, and not have to worry about turning them off individually when I’m done with them, potentially leaving resources around that cost money.

Looking at the template, there’s one sepecific AWS::Serverless::Function that does all of the IP address change work. This function takes an object as it’s payload with 2 keys: domains and ip. The domains parameter is a list of the domains that need updated and the ip parameter is the new IP address we’re updating it to.

Written in NodeJS, the function will first try to list all of the zones in route 53:

    // get all of the zones
    try{
        data = await route53.listHostedZones({}).promise();
    }catch (e) {
        console.error('listHostedZones error');
        console.error(e);
        return;
    }

and then attempt to loop over each zone looking for the A record within the zone, and to see if it matches the list of domains we sent over in the payload. If the domain matches, and the record is an A record, then an UPSERT command is added to an array to call back to route 53 after the array built.

        for(const resource of recordSetdata.ResourceRecordSets){
            if(resource.Type === "A" && payload.domains.indexOf(resource.Name) >= 0){
                // found a match, let's see if it needs updated
                if(resource.ResourceRecords[0].Value !== payload.ip){
                    if(!(zone.Id in upserts)){
                        upserts[zone.Id] = [];
                    }
                    // let's schedule it to be updated
                    upserts[zone.Id].push({
                        Action: "UPSERT",
                        ResourceRecordSet: {
                            Name: resource.Name,
                            ResourceRecords: [
                                {
                                    Value: payload.ip
                                }
                            ],
                            TTL: 60,
                            Type: "A"
                        }
                    });
                }
            }
        }

Setting an initial return value of “noting updated” (I’m now realizing is spelled wrong), I will create an array of promises to speed up the application and only wait for all of them to complete once vs waiting for each one to complete individually. I will return the strings of the update result after the primises are resolved.

    let returnVal = "noting updated";
    if(Object.keys(upserts).length > 0){
        let promises = [];
        for(const [zoneId, upsertStatement] of Object.entries(upserts)){
            promises.push(
                updateZone(zoneId, upsertStatement)
            );
        }
        returnVal = JSON.stringify(
            await Promise.all(promises)
        );
    }

I think the ingenious part of this last bit of code is the promises.push that will allow the main thread to continue, pushing the asynchronous tasks onto a separate thread. This way I only have to wait for them once!