This is a ROS 7 script (IIRC, the fetch command is bit different in ROS 6) which checks if the IP for an interface has changed, updates namecheap.com’s dynamic DNS and also sends you an email about it.
As it turns out, it’s also good for letting me know when electricity has been restored after a blackout or when the ISP comes back from an outage.
ROS now has /ip cloud with Mikrotik’s own ddns. With this script and Namecheap, you can have a much cooler and shorter ddns domain. And also a backup in case /ip cloud is down.
My first script here so please let me know if things can be improved. I’m quite the newbie to Mikrotik. The script was originally taken from the Mikrotik wiki for another ddns provider and then modified for Namecheap.
Email tool needs to be setup beforehand. Just change the variables to yours. There’s no error checking if the ddns update fails or sending email encounters an error.
Schedule this script to run every few minutes as you please. For pppoe, you can also run it from ppp profile up and down script.
:local "NC_DDNS_SERVER" "dynamicdns.park-your-domain.com"
:local "NC_DDNS_HOSTNAMES_ARRAY" {"host1";"host2"}
:local "NC_DDNS_HOSTNAMES" [:tostr $"NC_DDNS_HOSTNAMES_ARRAY"]
:local "NC_DDNS_DOMAIN" "example.com"
:local "NC_TOKEN" "0123456789abcdef"
:local "TELEGRAM_SERVER" "api.telegram.org"
:local "TELEGRAM_KEY" "bot0123456789abcdef"
:local "TELEGRAM_CHAT_ID" "012345678"
:local "ISP_NAME" "My_ISP"
:local "WAN_INTERFACE" "pppoe-out1"
:local "LOG_FILE_PREFIX" "/disk1/logs/DDNS_NC."
:global "ddns_previous_ip"
:local "ddns_current_ip" [ /ip address get [/ip address find interface=$"WAN_INTERFACE" ] address ]
:local "current_date" [/system clock get date]
:local "current_time" [/system clock get time]
:local "system_name" [/system identity get name]
:local "system_uptime" [/system resource get uptime]
:local "system_free_memory" [/system resource get free-memory]
:local "system_cpu_load" [/system resource get cpu-load]
:local "system_version" ("ROS " . [/system/package/get [find name=routeros] version] )
# Strip the net mask off the IP address
:set "ddns_current_ip" [:pick $"ddns_current_ip" 0 [:find $"ddns_current_ip" "/"]]
:if ([ :typeof $"ddns_previous_ip" ] = nil ) do={ :global "ddns_previous_ip" "0" }
:if ([ :typeof $"ddns_current_ip" ] = nil ) do={
:log info ("DDNS: No ip address present on $"WAN_INTERFACE" interface, please check.")
} else={
:if ($"ddns_current_ip" != $"ddns_previous_ip") do={
:foreach hostname in=$"NC_DDNS_HOSTNAMES_ARRAY" do={
:log info ("DDNS: Updating $hostname.$"NC_DDNS_DOMAIN" $"ddns_previous_ip" -> $"ddns_current_ip"")
:local str "https://$"NC_DDNS_SERVER"/update?host=$"hostname"&domain=$"NC_DDNS_DOMAIN"&password=$"NC_TOKEN"&ip=$"ddns_current_ip""
#:log info $str
/tool fetch url=$str mode=https dst-path=($"LOG_FILE_PREFIX".$hostname)
}
#:log info $"NC_DDNS_HOSTNAMES"
:log info "DDNS: Sending Email"
/tool e-mail send to=myself@example.com subject="$"ISP_NAME" IP $"current_date" $"current_time" $"system_name"" body="$"system_name" $"current_date" $"current_time" \r$"system_version"\rNamecheap: $"NC_DDNS_HOSTNAMES" \rDomain: $"NC_DDNS_DOMAIN" \r$"ISP_NAME" IP: $"ddns_current_ip" \rPrevious IP: $"ddns_previous_ip" \rUptime: $"system_uptime" \rFree memory: $"system_free_memory" kb \rCPU Load: $"system_cpu_load" % "
:log info "DDNS: Sending Telegram"
:local str "https://$"TELEGRAM_SERVER"/$"TELEGRAM_KEY"/sendMessage?chat_id=$"TELEGRAM_CHAT_ID"&parse_mode=Markdown&text=$"ISP_NAME" $"system_name" $"system_version"%0ANamecheap: $"NC_DDNS_HOSTNAMES"%0ADomain: $"NC_DDNS_DOMAIN"%0A$"current_date" $"current_time"%0A$"ISP_NAME" IP: $"ddns_current_ip"%0APrevious IP: $"ddns_previous_ip"%0AUptime: $"system_uptime"%0AFree memory: $"system_free_memory" kb%0ACPU Load: $"system_cpu_load" %"
#:log info $str
/tool fetch url=$str mode=https keep-result=no
:global "ddns_previous_ip" $"ddns_current_ip"
} else={
:log info "DDNS: IP has not changed. DDNS will not be updated."
}
}
Below is a snippet of code for updating Digitalocean’s DNS using its API. You can use this instead of Namecheap in the script above or both simultaneously. Fingers crossed DO’s DNS recordids are persistent.
# Only recordid(s) in DO_DDNS_RECORDID_ARRAY are used in Digitalocean's DNS API.
:local "DO_DDNS_RECORDID_ARRAY" {"host1"=123456789;"host2"=987654321}
:local "DO_DDNS_DOMAIN" "example.com"
:local "DO_TTL" 30
:local "DO_TOKEN" "dop_v1_abcdefghijklmn1234567890opqrstuvwxyz"
:local "DO_HEADER" "Content-Type: application/json,Authorization: Bearer $"DO_TOKEN""
:local "DO_LOG_FILE_PREFIX" "/disk1/logs/DDNS_DO."
:local "do_data" "{\"ttl\":$"DO_TTL", \"data\":\"$"ddns_current_ip"\"}"
:foreach hostname,recordid in=$"DO_DDNS_RECORDID_ARRAY" do={
:local "do_url" "https://api.digitalocean.com/v2/domains/$"DO_DDNS_DOMAIN"/records/$"recordid""
:log info $"do_url"
/tool fetch mode=https http-method=put http-header-field=$"DO_HEADER" http-data=$"do_data" url=$"do_url" dst-path=($"DO_LOG_FILE_PREFIX".$hostname)
}
I used the above as a starting point for my own namecheap dynamic DNS update script. I tied it into my DHCP client script for my WAN interface instead, so there is no need to run it every 5 minutes or anything like that. I also didn’t bother with the e-mail or keeping a pile of variables globally. Anyways, I thought I’d share it here since this is where I ended up when I wanted dynamic DNS with namecheap on my MikroTik router…
:if ($bound=1) do {
the name (within the domain) to update – must already exist!
this should to the deed! (note: no verification is done here, check namecheap yourself at first!)
/tool fetch url=$url mode=https keep-result=no
} else { #:log info “DHCP was unbound”
}You’ll want to login to namecheap first, lookup your DNS key for updates, and make an initial entry in your DNS table (of type “A + Dynamic DNS Record”.)
The script doesn’t do much (any) error checking, but I figure if it works once, it’ll probably keep working. And I don’t do anything when the DHCP is unbound.. I don’t think that hurts much of anything, plus I don’t know that we can remove a DNS entry remotely on namecheap.
So if you have more than one host name do you need to recreate this script for each sub domain host name? Or is there a way to enter all your sub domain host name in one script!
# the name (within the domain) to update -- must already exist!
:local hosts [:toarray value="record1,record2,record3"];
# the domain name
:local domain <domain>
# key for namecheap updates
:local password <pass key>
# get wan IP
:local ddnsip [ /ip address get [/ip address find interface=<wan interface> ] address ]
:log info "WAN IP is $ddnsip"
# Strip the net mask off the IP address
:for i from=( [:len $ddnsip] - 1) to=0 do={
:if ( [:pick $ddnsip $i] = "/") do={
:set ddnsip [:pick $ddnsip 0 $i]
}
}
:foreach host in $hosts do={
:local url "https://dynamicdns.park-your-domain.com/update?host=$host&domain=$domain&password=$password&ip=$ddnsip"
:log info "URL args: $url"
/tool fetch url=$url mode=https keep-result=no
}
I have been using it for last one year and it works so well. Recently, I have got ipv6 address from my isp. Is there a way to let the script support updating AAAA record ?
Hi,
I found that the original script does not work anymore and removed from wiki.
(I don’t know how long it is broken, because my ips are not changed.)
I did some debugging on command line, found that tool fetch with “address” will cause https problems.
Now namecheap only accept “url” mode.
/tool fetch url=$str mode=https keep-result=no
Additionally, original script export a lot of global variables, but only lastip is needed, others can be local.
Here’s the fixed script
:local ddnsserv "dynamicdns.park-your-domain.com"
:local ddnshostname "home"
:local ddnsdomain "yourdomain.com"
:local ddnspass "masked"
:local ddnsip [ /ip address get [/ip address find interface=pppoe-out1 ] address ]
:global ddnslastip
# Strip the net mask off the IP address
:for i from=( [:len $ddnsip] - 1) to=0 do={
:if ( [:pick $ddnsip $i] = "/") do={
:set ddnsip [:pick $ddnsip 0 $i]
}
}
:if ([ :typeof $ddnslastip ] = nil ) do={ :global ddnslastip "0" }
:if ([ :typeof $ddnsip ] = nil ) do={
:log info ("DDNS: No ip address present on pppoe interface.")
} else={
:if ($ddnsip != $ddnslastip) do={
:log info "DDNS: IP changed, previous IP=$ddnslastip, new IP=$ddnsip"
:log info ("DDNS: Updating Namecheap Dynamic DNS")
:local str "https://$ddnsserv/update?host=$ddnshostname&domain=$ddnsdomain&password=$ddnspass&ip=$ddnsip"
/tool fetch url=$str mode=https keep-result=no
:global ddnslastip $ddnsip
} else={
#:log info "DDNS: IP unchanged. No update required."
}
}
Hi folks,
have been trying to use the latest version of this script, however, can’t seem to get it working… copy/pasted the script, adjusted with my domain/subdomain/pw/interface, but nada.
Can anyone post a recent script that works ?
Many thanks.
L’ha già scritto che ha cambiato interfaccia. Lo script è pieno di errori, quindi a seconda della versione di RouterOS, che ovviamente nessuno scrive, hai risultati diversi.
I just wanted to chime in, that the script from 2017 when using DHCP-Client indeed works with the same logic.
I use it for my domain with minor cosmetic modifications:
:local host "*"
:local domain "my_domain"
:local password "my_api_key"
:if ($bound=1) do={
:local ipaddress $"lease-address"
:log info "IP lease received, updating DDNS"
/tool fetch url="https://dynamicdns.park-your-domain.com/update?host=$host&domain=$domain&password=$password&ip=$ipaddress" mode=https keep-result=no
} else={
:log info "No IP lease, nothing to be done with DDNS"
}
:log info "DHCP Client script ran successfully"
The last line is a sanity check, that the script really worked. Sadly the behaviour of scripts is to silently error out, and unless you do a log output at the very end, you can’t be sure that it really ran in full.
[edit]
Found it - just not able to test it with a bonded interface strangely … more testing.
For those following along at home - you must go to the DHCP Client in webfig, then open the interface (mine is a bond1 since it’s a bonded interface), and select “Advanced”. WHere there is a scripting option.
[edit]
ah, well, first thing is that the “host” should be the subdomain you have registered as an A+ DDNS record. So, change that to a meaningful name.
Next up, strangely why I can’t trigger the script when there is a DHCP refresh, the logs don’t show it running, and Namecheap does not update the IP for the domain.
[edit]
Checking the logs, note that you need to scroll to the END of the logs, they’re not sorts in reverse chronological order as you might expect, I am seeing proper error messages like this:
Which I assume means that we’re in a bit of a race condition here, DNS has not been applied yet and we need it to be. Checking DNS resolution after the DHCP address is acquired does appear to be ok –
[edit]
Yep, that was exactly it. The DNS was not set by the time I was trying to use a DNS name. Once I added a short delay in the script then the DNS name was updated properly.
Here’s the updated script:
:local host "subdomain"
:local domain "domain.com"
:local password "my_api_key"
:if ($bound=1) do={
:local ipaddress $"lease-address"
:delay 2000ms;
:log info "IP lease received, updating DDNS"
/tool fetch url="https://dynamicdns.park-your-domain.com/update?host=$host&domain=$domain&password=$password&ip=$ipaddress" mode=https keep-result=no
} else={
:log info "No IP lease, nothing to be done with DDNS"
}
:log info "DHCP Client script ran successfully"