What's new

DDNS Custom check External IP and refresh if necessary

  • SNBForums Code of Conduct

    SNBForums is a community for everyone, no matter what their level of experience.

    Please be tolerant and patient of others, especially newcomers. We are all here to share and learn!

    The rules are simple: Be patient, be nice, be helpful or be gone!

commodoro

Occasional Visitor
I wanted to share one of my projects with the forum, maybe it's useless and it's just a pure exercise in style, also considering that I'm not an expert in shell scripting.

The problem I wanted to solve is the following, I'am inside in a double NAT environment, with the Asus router in cascade with the one provided by my ISP, with static config for the WAN interface. I wanted to be sure that the DDNS was always aligned and updated if necessary, the provider that I have chosen is duckdns.org.
I noticed that using this configuration, if my ISP changes my public IP, my RT-AX58U does not recognize the change as the WAN port does not change state, and therefore all WAN reboot processes are not triggered, including the DDNS update.

Hence the idea of creating the following script, which does nothing other than retrive the IP inside the inadyn cache file and, based on a list, invokes online services to know the external IP, compares the two values and if they are different refresh the DDNS.

Bash:
#!/bin/sh
#############################################################################################################
#                       Based list check external IP and DDNS refresh for Asuswrt-Merlin                    #
#                                               By Commodoro                                                #
#                                            20/04/2024 - v1.0.0                                            #
#############################################################################################################

#set -x

CACHEPATH=/var/cache/inadyn/
ONLYNAME=$(basename "$0" .sh)
LOCKFILE=/tmp/lock/$ONLYNAME.lock
LOCKFILE=~/"${ONLYNAME}".lock
REGEXVALIDIP='^(([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\.){3}([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$'
URLS='https://api.seeip.org https://myip.dnsomatic.com/ https://api.ipify.org https://api.my-ip.io/v2/ip.txt https://ipv4.getmyip.dev https://ident.me'

logger_helper() {
  logger -st "customDdnsRefresh[$$]" "$1"
}

if [ -e "${LOCKFILE}" ] && kill -0 "$(cat "${LOCKFILE}")"; then
    logger_helper "already running"
    exit 1
fi

# make sure the lockfile is removed when we exit and then claim it
trap 'rm -f "${LOCKFILE}"; exit' INT TERM EXIT
echo $$ > "${LOCKFILE}"


directory_exist_and_not_empty() {

    if [ -z "$*" ]
    then
        logger_helper "No path to check, please verify the call to directory_exist_and_not_empty function in script code"
        return 1
    else
        if [ -d "$1" ] && [ "$(ls -A "$1")" ]
        then
            return 0
        else
            logger_helper "Directory $1 not exist or is empty, nothing to do"
            return 1
        fi
    fi
}

invoke_remote_ip_check() {

  #a sort of load balancing for the call of the check sites, based on the minute of running of the script and on the length of the vector of the sites to be invoked
  MINUTE=$(date +"%M")
  SIZE=$(echo "${URLS}" | awk '{while (match($0, / /)) {count++; $0=substr($0, RSTART+RLENGTH)}} END {print count+1}')
  INDEX=$((MINUTE%SIZE))

  #since in busybox there is no access to the vector on an index basis, workaround with for and checking the position of the element with the index calculated above
  LOOP=0
  for url in ${URLS}; do
    if [ ${LOOP} -eq ${INDEX} ]; then
      logger_helper "use service ${url}"
      #Save response of curl
      RESP=$(curl -fs "${url}" 2>&1)

      #remove no useful data from response and verify is it a valid IP
      IP="$( echo "${RESP}" | head -n 1 | grep -Eo "${REGEXVALIDIP}")"

      if [ -n "$IP" ]; then
        #check if content inadyn.cache is the same
        if [ "$IP" = "$(cat "${CACHEPATH}"/"$1")" ]; then
          return 0
        else
          return 2
        fi
      else
         LOOP=$((LOOP+1))
      fi
    else
      LOOP=$((LOOP+1))
    fi
  done

  return 1
}

if directory_exist_and_not_empty "${CACHEPATH}"; then

  FILES=$(ls "${CACHEPATH}")
  invoke_remote_ip_check "${FILES}"
  RET="$?"

  if [ "${RET}" -eq 0 ]; then
    logger_helper "DDNS are still valid"
  elif [ "${RET}" -eq 2 ]; then
    logger_helper "$(service restart_ddns ) DDNS refreshed"
  else
    logger_helper "Error invoke url to check external ip"
  fi

else
 logger_helper "DDNS not enabled"
fi

exit 0

To install

  1. log in with ssh to the router
  2. move to the /jffs/scripts/ directory
    Bash:
    cd /jffs/script/
  3. create an empty file named customDdnsRefresh.sh
    Bash:
    vi customDdnsRefresh.sh
  4. copy the code inside the file
  5. issue the authorization for execution
    Bash:
    chmod 755 customDdnsRefresh.sh
  6. add the schedule you want to the cru, I use it every minute
    Bash:
    cru a ddns_refresh "*/1 * * * * /jffs/scripts/customDdnsRefresh.sh"
  7. To confirm that everything is working correctly, you can see the following entry in the log:
    Code:
    April 20 19:52:00 customDdnsRefresh[2430]: use the service https://ipv4.getmyip.dev
    April 20 19:52:01 customDdnsRefresh[2430]: DDNS are still valid

I hope this can be useful for someone

Greetings
Commodoro
 
Last edited:

Latest threads

Sign Up For SNBForums Daily Digest

Get an update of what's new every day delivered to your mailbox. Sign up here!
Top