Dynamic DNS using Linode and local powershell script

After noip.com and dyndns.org shut their doors to all but non-paying customers, I spent many years without being able to reliably access my home network from outside. Of course, it was always possible to do without dynamic DNS by making a note of the current IP address and use this for as long as the address stayed the same, but sods law I would *really* need to access home the day that the router was upgraded remotely or the address expired or whatever.

Over these years I looked at various options, and almost settled on installing BIND9 and using nsupdate on a remote machine and writing a script on the local network to check-in periodically.

This seemed like a good option because I already pay monthly for a linux VPS so there was no extra outlay. Deep down, though, I knew it was overly complicated and would probably break someday because I don’t have the time to maintain my VPS in a “mission critical” way, or I’d rebuild the server and forget it took me 4 hours to setup the solution, thus ending up not having it for another 6 months because I couldn’t be bothered to go through the effort again.

Turns out there was much more of a tidy solution for me. I use Linode.com to host my VPS, and I also use its excellent DNS manager control panel for all of my domains. Sure, you can use the DNS manager of your domain registrar and create A records and the like to point to your Linode’s IP, but it’s so much nicer pointing your NS servers to Linode and managing all your DNS settings from Linode. The interface is designed beautifully, the account security is top-notch, not to mention there’s a very sweet iPhone app that makes creating records / cloning zones a breeze.

I digress. My solution involves creating a Linode API key, pulling-out the domain and resource IDs from Linode, then having a powershell script which runs on a local Windows box update these when it detects a change of address.

I won’t bother going in to the former as you can work that out, but the latter is certainly something worth sharing here.

After nicking the IPChangeNotify powershell script from somewhere on the web, I discovered a whole load of reliability weaknesses, which I’ve pretty much accounted for. Without further ado, here’s the spec of my updated script and solution as a whole

  • relies on Windows task scheduler
  • (ironically) uses dyndns IP address checker at http://checkip.dyndns.com/
  • if for some reason the above doesn’t respond in a timely manner, it waits, tries again, waits, etc.
  • updates are of course made securely, over https
  • email notifications telling you a whole load of useful info such as:
    • how many attempts it took to discover the ip address
    • number of milliseconds it took to discover the address (for fun / to help diagnose problems)
    • old ip address
    • new ip address if changed
    • response from Linode API to confirm that the A record was updated
    • email subject based on change / success / failure to allow for sensible rules in Gmail
  • write-out the IP address to Dropbox (added bonus – you get a little Windows notification when this text file is changed) – just in case something goes wrong and you can’t be bothered to sift through notification emails

All you need to make this work is:

  • a Linode account
  • Windows machine on your local network that stays on all the time
  • the below script

(Apologies for the godawful formatting, but it should copy/paste okay!)

$scriptpath = $MyInvocation.MyCommand.Definition
[string]$dir = Split-Path $scriptpath
set-location $dir

$oldip = gc .\ip.txt
Write-Host “IP stored in Dropbox: $oldip”
Write-Host “Getting current IP…”

$firststartTime = get-date
$webpage = (New-Object net.webclient).downloadstring(“http://checkip.dyndns.com”)
$firstendTime = get-date
$timetogetpage = ($firstendTime – $firststartTime).TotalSeconds
$currentip = $webpage -replace “[^\d\.]”
Write-Host $currentip
Write-Host “That took” $timetogetpage “seconds”

if ($timetogetpage -le 2) {
$triestext = “(on first attempt)”
} else {
$triestext = “, not on the first attempt:`n`nTry 1 ($currentip) = $timetogetpage`n”
}
$tries = 1
while ($timetogetpage -gt 2) {
Write-Host “Let’s try again”
$subsequentstartTime = get-date
$webpage = (New-Object net.webclient).downloadstring(“http://checkip.dyndns.com”)
$subsequentendTime = get-date
$timetogetpage = ($subsequentendTime – $subsequentstartTime).TotalSeconds
$currentip = $webpage -replace “[^\d\.]”
$tries++
$triestext = $triestext + “Try $tries ($currentip) = $timetogetpage `n”
Write-Host “Try $tries ($currentip) = $timetogetpage”
}

$smtpServer = “smtp.gmail.com”
$sender = “sender-email@gmail.com”
$users = “recipient-email@gmail.com”, “anotheruser@gmail.com”
$subject = “”

# change the below to match the ID of the given record
$resourceid=’1234567′
# change api_key and domainid below to match your Linode.com API key and ID of the domain
$updateurl=”https://api.linode.com/?api_key=abcdefg&api_action=domain.resource.update&domainid=123456&resourceid=$resourceid&target=$currentip”

if ($timetogetpage -lt 3) {
if ($oldip -eq $currentip) {
# Discovery success – IP stayed the same (ideal case)
$subject = $subject + “IP address found: $currentip. No change! (discovery took $timetogetpage seconds)”
$body = “Old IP = $oldip`nNew IP = $currentip`n`nNo change!`n`nDiscovery took $timetogetpage seconds $triestext”
Write-Host “Not updating the file” -ForegroundColor Gray

} else {
# Discovery success – IP changed (important case)
$subject = $subject + “IP change detected: $currentip. Discovery took $timetogetpage seconds”
$body = “Old IP = $oldip`nNew IP = $currentip`n`nWe updated the DNS!`n`nDiscovery took $timetogetpage seconds $triestext”
$currentip | Out-File .\ip.txt -Force
Write-Host “New IP saved in file is: $currentip” -ForegroundColor Green
}

Write-Host “Updating DNS…”
$updatewebpage = (New-Object net.webclient).downloadstring($updateurl)
$body = $body + “`n`nOutput of the DNS Update service:`n`n$updatewebpage”
if ($updatewebpage -like “*Invalid*”) {
$body = $body + “`n`nLinode response: DID NOT UPDATE. Check Linode.com.”
} else {
$body = $body + “`n`nLinode response: UPDATED”
}
Write-Host “Output of the DNS update service: $updatewebpage”

} else {
# Discovery failed
$subject = $subject + “IP address kept as $currentip (discovery failed and took $timetogetpage seconds)”
$body = “Old IP = $oldip`nNew IP = $currentip`n`nI’m suspicious, discovery took $timetogetpage seconds $triestext”
Write-Host “IP address supposedly found, but it took $timetogetpage seconds” -ForegroundColor Red
Write-Host “Keeping the current IP: $currentip” -ForegroundColor Red
}

# Finally send the email
Write-Host “Sending an email now…” -ForegroundColor Green
foreach ($user in $users) {
Write-Host “Sending email notification to $user” -ForegroundColor Green
$smtp = New-Object Net.Mail.SmtpClient($smtpServer, 587)
$smtp.EnableSsl = $true
$smtp.Credentials = New-Object System.Net.NetworkCredential(“sender-email@gmail.com”, “email-password”);
$smtp.Send($sender, $user, $subject, $body)
}

 

 

 

 

2 comments

  1. Thanks for mentioning No-IP in your article! I just wanted to reach out and clarify that we still do have an entirely free dynamic DNS service. With our Free Dynamic DNS, we allow you to create up to 3 hostnames. We only ask that you confirm your hostname every 30 days, so we can keep our database clear of unused hostnames. To learn more about our free dynamic DNS service you can visit, http://www.noiop.com/free. Thanks again!

  2. I switched to using dns.he.net a few years ago, 100% free, they allow HTTP updating of DDNS entries for both IPv4 and IPv6 and on your own domain names. Might be worth a look.

Leave a Reply

Your email address will not be published. Required fields are marked *