/ programming

Dynamic DNS with CloudFlare via the API

So BT are super helpful and update my IP address all the time.

This makes running my VPN from my NAS for accessing stuff locally while remote quite tricky.

So, in learning some golang, I decided to write a small app that I could run on the NAS on a cron to contact CloudFlare, the DNS for tk.gg, find the subdomain for my local servers, and update it with the current IP address.

It's super basic and quite dumb (and relies on an external service when I could sniff my network packet) but worth putting up.

To do this we will contact a service to get our IP, then contact CloudFlare and update the record. We'll use CloudFlare's API first to get the IDs that we need in the future, and then ditch it and bake it into the app because I'm lazy.

main.go

import (
	"fmt"
	"log"

	"github.com/cloudflare/cloudflare-go"
	"gopkg.in/resty.v1"
)

First import format and log, for error handling and testing, and then resty for managing the API request for your IP address (again, a bit lazy).

func main() {

	ip, err := resty.R().Get("http://ipv4bot.whatismyipaddress.com")
    	if err != nil {
		log.Fatal(err)
	}

Start the main function and make a Resty request for your IP, which I assign to the ip variable. I also tried https://api.ipify.org but for some reason this was blocked from the coffee shop I ended up writing this at.

IF you want to do this with inspecting a request, StackOverflow has a good answer.

	ipString := ip.String()

Then we need to create a new variable of a new type as the ip variable is a Resty response, and we need the IP as a string for the CloudFlare request below. In Go variable types are immutable, so we can reassign a variable to another value of the same type, but not from a Resty response to a string.

	api, err := cloudflare.New("API KEY HERE", "ACCOUNT NAME HERE")
	if err != nil {
		log.Fatal(err)
	}

Initialise your CloudFlare API. You can get your keys here.

	record := cloudflare.DNSRecord{
		Content: ipString,
	}

It's time to create a new DNS record. CloudFlare will take any current record and do a replacement on the properties you had added, so don't worry about missing most of the struct.

All we need to do is change the Content field to the IP of the originating server as a string.

	id, err := api.ZoneIDByName("tk.gg")
    	if err != nil {
		log.Fatal(err)
	}

Now we get the Zone ID (domain ID) for the zone which contains the subdomain you wish to modify. In this case mine is easy. I assign this to the id variable.


   recordID := "YOUR RECORD ID"

Then we need to get the record ID. The best way of doing this is to run and log out the list of records for the zone, find the ID, and copy it in. You can do this with the DNSRecords function from the CloudFlare go library. It's documented here.

	err = api.UpdateDNSRecord(id, recordID, record)
	if err != nil {
		log.Fatal(err)
	}

And then we do the update! This will only return an error if it fails, and will otherwise return nothing. We do a regular assign rather than an assign and create here as err already exists.

}

And we close the function!

Then I found I needed to build this for the architecture of my NAS, which is different to my Mac: the GOOS environment variable is darwin for MacOS, whereas for QNAP it is linux.

To change this we pass the environment variables before we run go build

env GOOS=linux GOARCH=amd64 go build main.go

And there you will have main ready to be put in your cronjob.