CloudFlare Dynamic DNS using OpenWRT
I use dynamic DNS for my home internet connection so that I can access the machines from anywhere on the internet. And I use OpenWRT on my router. Earlier I was using Namecheap for managing DNS but I switched to CloudFlare for performance and security reasons of the website.
Unfortunately CloudFlare doesn’t support updating IP via shell script — well, it sort of does but the JSON stuff gets very messy with quoting in shell scripts, so I wrote a Lua script to update my IP whenever my PPPoE connection starts up; I have dropped the script in /etc/ppp/ip-up.d so it gets executed by pppd whenever my connection comes up. You can run this script via cron or put it /etc/hotplug if you wish to. This script uses LuaSocket, LuaSec, JSON4Lua and libubus-lua libraries that are easily installable on an OpenWRT router with 4 MB flash memory.
Now I can have the benefits of CloudFlare without losing out on DDNS :D. Here’s the code:
#!/usr/bin/lua
-- For use with openwrt since openwrt supports LUA.
-- Prerequisites:
-- luasec
-- luasocket
-- libubus-lua
-- json4lua
-- I have a PPPoE connection so I just drop this script in /etc/ppp/ip-up.d
-- You can run via crontab or put it in interface hotplug 🙂
update_records = {"ddns"}
domain_name = "domain.com"
function log(msg)
os.execute("logger -t updateip '" .. msg .. "'")
end
require ("os")
json = require ("json")
require ("socket")
https = require ("ssl.https")
require ("ltn12")
reqheaders = {
["X-Auth-Email"] = "EMAIL_ID",
["X-Auth-Key"] = "API_KEY",
["Content-Type"] = "application/json",
}
zones = {}
success = https.request({
url = "https://api.cloudflare.com/client/v4/zones?name=" .. domain_name,
sink = ltn12.sink.table(zones),
method = "GET",
headers = reqheaders,
})
if not success then
log('Failed to fetch zones')
os.exit(1)
end
zones = json.decode(zones[1])
if not zones.success then
log('Zone fetch failed')
os.exit(1)
end
zone_id = zones.result[1].id
for i, name in ipairs(update_records) do
if name ~= domain_name then
update_records[i] = string.format("%s.%s", name, domain_name)
end
end
record_filter = "name=" .. table.concat(update_records, ",")
records = {}
success = https.request({
url = "https://api.cloudflare.com/client/v4/zones/" .. zone_id .. "/dns_records?type=A&" .. record_filter,
sink = ltn12.sink.table(records),
method = "GET",
headers = reqheaders
})
if not success then
log('Failed to fetch dns records')
os.exit(1)
end
records = json.decode(records[1])
if not records.success then
log('Record fetch failed')
os.exit(1)
end
update_records_1 = {}
require ("ubus")
u = ubus.connect()
if not u then
log('Ubus connect failed')
os.exit(1)
end
status = u:call("network.interface.wan", "status", {})
ip = status["ipv4-address"][1]["address"]
for _, record in pairs(records.result) do
for _, name in pairs(update_records) do
if name == record.name and record.content ~= ip then
record.content = ip
table.insert(update_records_1, record)
end
end
end
for _, record in pairs(update_records_1) do
encoded_update = json.encode(record)
response = {}
reqheaders["content-length"] = string.len(encoded_update)
success = https.request({
url = "https://api.cloudflare.com/client/v4/zones/" .. zone_id .. "/dns_records/" .. record.id,
method = "PUT",
headers = reqheaders,
sink = ltn12.sink.table(response),
source = ltn12.source.string(encoded_update)
})
if not success then
log("Failed to update " .. record.name)
else
response = json.decode(response[1])
if not response.success then
log("Failed to update " .. record.name)
else
log("Updated " .. record.name)
end
end
end
Suggestions? Post in comments or fork on GitHub.