[ddns-scripts] Update to Version 2.0.1-1
authorChristian Schoenebeck <christian.schoenebeck@gmail.com>
Sun, 21 Sep 2014 15:56:05 +0000 (17:56 +0200)
committerChristian Schoenebeck <christian.schoenebeck@gmail.com>
Sun, 21 Sep 2014 15:56:05 +0000 (17:56 +0200)
rewritten

Sorry GitHub web interface only supports single commits that will be summarized into one pull request.
Signed-off-by: Christian Schoenebeck <christian.schoenebeck@gmail.com>
net/ddns-scripts/files/usr/lib/ddns/dynamic_dns_updater.sh

index e6d298740d973de2624754a68749ff7eb23cfbd4..20e40b310de3f4db0ac7e50b61a72182cd56c495 100755 (executable)
 #!/bin/sh
-# /usr/lib/dynamic_dns/dynamic_dns_updater.sh
+# /usr/lib/ddns/dynamic_dns_updater.sh
 #
-# Written by Eric Paul Bishop, Janary 2008
+# Original written by Eric Paul Bishop, January 2008
 # Distributed under the terms of the GNU General Public License (GPL) version 2.0
-#
-# This script is (loosely) based on the one posted by exobyte in the forums here:
+# (Loosely) based on the script on the one posted by exobyte in the forums here:
 # http://forum.openwrt.org/viewtopic.php?id=14040
 #
-
-. /usr/lib/ddns/dynamic_dns_functions.sh
-
-
-service_id=$1
-if [ -z "$service_id" ]
-then
-       echo "ERRROR: You must specify a service id (the section name in the /etc/config/ddns file) to initialize dynamic DNS."
-       return 1
-fi
-
-#default mode is verbose_mode, but easily turned off with second parameter
-verbose_mode="1"
-if [ -n "$2" ]
-then
-       verbose_mode="$2"
-fi
-
-###############################################################
-# Leave this comment here, to clearly document variable names
-# that are expected/possible
+# extended and partial rewritten by Christian Schoenebeck in August 2014 to support:
+# - IPv6 DDNS services
+# - DNS Server to retrieve registered IP including TCP transport
+# - Proxy Server to send out updates
+# - force_interval=0 to run once
+# - the usage of BIND's host command instead of BusyBox's nslookup if installed
+# - extended Verbose Mode and log file support for better error detection 
 #
-# Now use load_all_config_options to load config
-# options, which is a much more flexible solution.
+# variables in small chars are read from /etc/config/ddns
+# variables in big chars are defined inside these scripts as global vars
+# variables in big chars beginning with "__" are local defined inside functions only
+#set -vx       #script debugger
+
+[ $# -lt 1 -o -n "${2//[0-3]/}" -o ${#2} -gt 1 ] && {
+       echo -e "\n  USAGE:"
+       echo -e "  $0 [SECTION] [VERBOSE_MODE]\n"
+       echo    "  [SECTION]      - service section as defined in /etc/config/ddns"
+       echo    "  [VERBOSE_MODE] - '0' NO output to console"
+       echo    "                   '1' output to console"
+       echo    "                   '2' output to console AND logfile"
+       echo    "                       + run once WITHOUT retry on error"
+       echo    "                   '3' output to console AND logfile"
+       echo    "                       + run once WITHOUT retry on error"
+       echo -e "                       + NOT sending update to DDNS service\n"
+       exit 1
+}
+
+. /usr/lib/ddns/dynamic_dns_functions.sh       # global vars are also defined here
+
+SECTION_ID="$1"
+VERBOSE_MODE=${2:-1}   #default mode is log to console
+
+# set file names
+PIDFILE="$RUNDIR/$SECTION_ID.pid"      # Process ID file
+UPDFILE="$RUNDIR/$SECTION_ID.update"   # last update successful send (system uptime)
+LOGFILE="$LOGDIR/$SECTION_ID.log"      # log file
+
+# VERBOSE_MODE > 1 delete logfile if exist to create an empty one
+# only with this data of this run for easier diagnostic
+# new one created by verbose_echo function
+[ $VERBOSE_MODE -gt 1 -a -f $LOGFILE ] && rm -f $LOGFILE
+
+################################################################################
+# Leave this comment here, to clearly document variable names that are expected/possible
+# Use load_all_config_options to load config options, which is a much more flexible solution.
 #
+# config_load "ddns"
+# config_get <variable> $SECTION_ID <option]>
 #
-#config_load "ddns"
+# defined options (also used as variable):
+# 
+# enable       self-explanatory
+# interface    network interface used by hotplug.d i.e. 'wan' or 'wan6'
 #
-#config_get enabled $service_id enabled
-#config_get service_name $service_id service_name
-#config_get update_url $service_id update_url
+# service_name Which DDNS service do you use or "custom"
+# update_url   URL to use to update your "custom" DDNS service
 #
+# domain       Your DNS name / replace [DOMAIN] in update_url
+# username     Username of your DDNS service account / replace [USERNAME] in update_url
+# password     Password of your DDNS service account / replace [PASSWORD] in update_url
 #
-#config_get username $service_id username
-#config_get password $service_id password
-#config_get domain $service_id domain
+# use_https    use HTTPS to update DDNS service
+# cacert       file or directory where HTTPS can find certificates to verify server; 'IGNORE' ignore check of server certificate
 #
+# use_syslog   log activity to syslog
 #
-#config_get use_https $service_id use_https
-#config_get use_syslog $service_id use_syslog
-#config_get cacert $service_id cacert
+# ip_source    source to detect current local IP ('network' or 'web' or 'script' or 'interface')
+# ip_network   local defined network to read IP from i.e. 'wan' or 'wan6'
+# ip_url       URL to read local address from i.e. http://checkip.dyndns.com/ or http://checkipv6.dyndns.com/
+# ip_script    full path and name of your script to detect local IP
+# ip_interface physical interface to use for detecting 
 #
-#config_get ip_source $service_id ip_source
-#config_get ip_interface $service_id ip_interface
-#config_get ip_network $service_id ip_network
-#config_get ip_url $service_id ip_url
+# check_interval       check for changes every  !!! checks below 10 minutes make no sense because the Internet 
+# check_unit           'days' 'hours' 'minutes' !!! needs about 5-10 minutes to sync an IP-change for an DNS entry
 #
-#config_get force_interval $service_id force_interval
-#config_get force_unit $service_id force_unit
+# force_interval       force to send an update to your service if no change was detected
+# force_unit           'days' 'hours' 'minutes' !!! force_interval="0" runs this script once for use i.e. with cron
 #
-#config_get check_interval $service_id check_interval
-#config_get check_unit $service_id check_unit
-#########################################################
-load_all_config_options "ddns" "$service_id"
-
-
-#some defaults
-if [ -z "$check_interval" ]
-then
-       check_interval=600
-fi
-
-if [ -z "$retry_interval" ]
-then
-       retry_interval=60
-fi
-
-if [ -z "$check_unit" ]
-then
-       check_unit="seconds"
-fi
-
-if [ -z "$force_interval" ]
-then
-       force_interval=72
-fi
-
-if [ -z "$force_unit" ]
-then
-       force_unit="hours"
-fi
-
-if [ -z $use_syslog ]
-then
-       use_syslog=0
-fi
-
-if [ -z "$use_https" ]
-then
-       use_https=0
-fi
-
-
-#some constants
-
-retrieve_prog="/usr/bin/wget -O - ";
-if [ "x$use_https" = "x1" ]
-then
-       /usr/bin/wget --version 2>&1 |grep -q "\+ssl"
-       if [ $? -eq 0 ]
-       then
-               if [ -f "$cacert" ]
-               then
-                       retrieve_prog="${retrieve_prog}--ca-certificate=${cacert} "
-               elif [ -d "$cacert" ]
-               then
-                       retrieve_prog="${retrieve_prog}--ca-directory=${cacert} "
-               fi
-       else
-               retrieve_prog="/usr/bin/curl "
-               if [ -f "$cacert" ]
-               then
-                       retrieve_prog="${retrieve_prog}--cacert $cacert "
-               elif [ -d "$cacert" ]
-               then
-                       retrieve_prog="${retrieve_prog}--capath $cacert "
-               fi
-       fi
-fi
-
-
-service_file="/usr/lib/ddns/services"
-
-ip_regex="[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}"
-
-NEWLINE_IFS='
-'
-
-#determine what update url we're using if the service_name is supplied
-if [ -n "$service_name" ]
-then
-       #remove any lines not containing data, and then make sure fields are enclosed in double quotes
-       quoted_services=$(cat $service_file |  grep "^[\t ]*[^#]" |  awk ' gsub("\x27", "\"") { if ($1~/^[^\"]*$/) $1="\""$1"\"" }; { if ( $NF~/^[^\"]*$/) $NF="\""$NF"\""  }; { print $0 }' )
-
-
-       #echo "quoted_services = $quoted_services"
-       OLD_IFS=$IFS
-       IFS=$NEWLINE_IFS
-       for service_line in $quoted_services
-       do
-               #grep out proper parts of data and use echo to remove quotes
-               next_name=$(echo $service_line | grep -o "^[\t ]*\"[^\"]*\"" | xargs -r -n1 echo)
-               next_url=$(echo $service_line | grep -o "\"[^\"]*\"[\t ]*$" | xargs -r -n1 echo)
-
-               if [ "$next_name" = "$service_name" ]
-               then
-                       update_url=$next_url
-               fi
-       done
-       IFS=$OLD_IFS
-fi
-
-if [ "x$use_https" = x1 ]
-then
-       update_url=$(echo $update_url | sed -e 's/^http:/https:/')
-fi
-
-verbose_echo "update_url=$update_url"
-
-#if this service isn't enabled then quit
-if [ "$enabled" != "1" ] 
-then
-       return 0
-fi
-
-#compute update interval in seconds
-case "$force_unit" in
-       "days" )
-               force_interval_seconds=$(($force_interval*60*60*24))
-               ;;
-       "hours" )
-               force_interval_seconds=$(($force_interval*60*60))
-               ;;
-       "minutes" )
-               force_interval_seconds=$(($force_interval*60))
-               ;;
-       "seconds" )
-               force_interval_seconds=$force_interval
-               ;;
-       * )
-               #default is hours
-               force_interval_seconds=$(($force_interval*60*60))
-               ;;
-esac
-
-
-#compute check interval in seconds
-case "$check_unit" in
-       "days" )
-               check_interval_seconds=$(($check_interval*60*60*24))
-               ;;
-       "hours" )
-               check_interval_seconds=$(($check_interval*60*60))
-               ;;
-       "minutes" )
-               check_interval_seconds=$(($check_interval*60))
-               ;;
-       "seconds" )
-               check_interval_seconds=$check_interval
-               ;;
-       * )
-               #default is seconds
-               check_interval_seconds=$check_interval
-               ;;
-esac
-
-
-#compute retry interval in seconds
-case "$retry_unit" in
-       "days" )
-               retry_interval_seconds=$(($retry_interval*60*60*24))
-               ;;
-       "hours" )
-               retry_interval_seconds=$(($retry_interval*60*60))
-               ;;
-       "minutes" )
-               retry_interval_seconds=$(($retry_interval*60))
-               ;;
-       "seconds" )
-               retry_interval_seconds=$retry_interval
-               ;;
-       * )
-               #default is seconds
-               retry_interval_seconds=$retry_interval
-               ;;
+# retry_interval       if error was detected retry in
+# retry_unit           'days' 'hours' 'minutes' 'seconds'
+# retry_count          #NEW# number of retries before scripts stops
+#
+# use_ipv6             #NEW# detecting/sending IPv6 address
+# force_ipversion      #NEW# force usage of IPv4 or IPv6 for the whole detection and update communication
+# dns_server           #NEW# using a non default dns server to get Registered IP from Internet
+# force_dnstcp         #NEW# force communication with DNS server via TCP instead of default UDP
+# proxy                        #NEW# using a proxy for communication !!! ALSO used to detect local IP via web => return proxy's IP !!!
+# use_logfile          #NEW# self-explanatory "/var/log/ddns/$SECTION_ID.log"
+#
+# some functionality needs 
+# - GNU Wget or cURL installed for sending updates to DDNS service
+# - BIND host installed to detect Registered IP
+#
+################################################################################
+
+# verify and load SECTION_ID is exists
+[ "$(uci_get ddns $SECTION_ID)" != "service" ] && {
+       [ $VERBOSE_MODE -le 1 ] && VERBOSE_MODE=2       # force console out and logfile output
+       [ -f $LOGFILE ] && rm -f $LOGFILE               # clear logfile before first entry
+       verbose_echo "\n ************** =: ************** ************** **************"
+       verbose_echo "       STARTED =: PID '$$' at $(eval $DATE_PROG)"
+       verbose_echo "    UCI CONFIG =:\n$(uci -q show ddns | grep '=service' | sort)"
+       critical_error "Service '$SECTION_ID' not defined"
+}
+load_all_config_options "ddns" "$SECTION_ID"
+
+verbose_echo "\n ************** =: ************** ************** **************"
+verbose_echo "       STARTED =: PID '$$' at $(eval $DATE_PROG)"
+syslog_info "Started"
+case $VERBOSE_MODE in
+       0) verbose_echo "  verbose mode =: '0' - run normal, NO console output";;
+       1) verbose_echo "  verbose mode =: '1' - run normal, console mode";;
+       2) verbose_echo "  verbose mode =: '2' - run once, NO retry on error";;
+       3) verbose_echo "  verbose mode =: '3' - run once, NO retry on error, NOT sending update";;
+       *) critical_error "ERROR detecting VERBOSE_MODE '$VERBOSE_MODE'"
 esac
+verbose_echo "    UCI CONFIG =:\n$(uci -q show ddns.$SECTION_ID | sort)"
+
+# set defaults if not defined
+[ -z "$enabled" ]        && enabled=0
+[ -z "$retry_count" ]    && retry_count=5
+[ -z "$use_syslog" ]      && use_syslog=0      # not use syslog
+[ -z "$use_https" ]       && use_https=0       # not use https
+[ -z "$use_logfile" ]     && use_logfile=1     # NEW - use logfile by default
+[ -z "$use_ipv6" ]       && use_ipv6=0         # NEW - use IPv4 by default
+[ -z "$force_ipversion" ] && force_ipversion=0 # NEW - default let system decide
+[ -z "$force_dnstcp" ]   && force_dnstcp=0     # NEW - default UDP
+[ -z "$ip_source" ]      && ip_source="network"
+[ "$ip_source" = "network" -a -z "$ip_network" -a $use_ipv6 -eq 0 ] && ip_network="wan"  # IPv4: default wan
+[ "$ip_source" = "network" -a -z "$ip_network" -a $use_ipv6 -eq 1 ] && ip_network="wan6" # IPv6: default wan6
+[ "$ip_source" = "web" -a -z "$ip_url" -a $use_ipv6 -eq 0 ] && ip_url="http://checkip.dyndns.com"
+[ "$ip_source" = "web" -a -z "$ip_url" -a $use_ipv6 -eq 1 ] && ip_url="http://checkipv6.dyndns.com"
+[ "$ip_source" = "interface" -a -z "$ip_interface" ] && ip_interface="eth1"
+
+# check configuration and enabled state
+[ -z "$domain" -o -z "$username" -o -z "$password" ] && critical_error "Service Configuration not correctly configured"
+[ $enabled -eq 0 ] && critical_error "Service Configuration is disabled"
+
+# verify script if configured and executable
+if [ "$ip_source" = "script" ]; then
+       [ -z "$ip_script" ] && critical_error "No script defined to detect local IP"
+       [ -x "$ip_script" ] || critical_error "Script to detect local IP not found or not executable"
+fi
 
+# compute update interval in seconds
+get_seconds CHECK_SECONDS ${check_interval:-10} ${check_unit:-"minutes"} # default 10 min
+get_seconds FORCE_SECONDS ${force_interval:-72} ${force_unit:-"hours"}  # default 3 days
+get_seconds RETRY_SECONDS ${retry_interval:-60} ${retry_unit:-"seconds"} # default 60 sec
+verbose_echo "check interval =: $CHECK_SECONDS seconds"
+verbose_echo "force interval =: $FORCE_SECONDS seconds"
+verbose_echo "retry interval =: $RETRY_SECONDS seconds"
+verbose_echo " retry counter =: $retry_count times"
 
-verbose_echo "force seconds = $force_interval_seconds"
-verbose_echo "check seconds = $check_interval_seconds"
+# determine what update url we're using if a service_name is supplied
+# otherwise update_url is set inside configuration (custom service)
+[ -n "$service_name" ] && get_service_url update_url
+[ -z "$update_url" ]   && critical_error "no update url found/defined"
 
 #kill old process if it exists & set new pid file
-if [ -d /var/run/dynamic_dns ]
-then
+if [ -d $RUNDIR ]; then
        #if process is already running, stop it
-       if [ -e "/var/run/dynamic_dns/$service_id.pid" ]
-       then
-               old_pid=$(cat /var/run/dynamic_dns/$service_id.pid)
-               test_match=$(ps | grep "^[\t ]*$old_pid")
-               verbose_echo "old process id (if it exists) = \"$test_match\""
-               if [ -n  "$test_match" ]
-               then
-                       kill $old_pid
-               fi
+       if [ -e "$PIDFILE" ]; then
+               OLD_PID=$(cat $PIDFILE)
+               ps | grep -q "^[\t ]*$OLD_PID" && {
+                       verbose_echo "   old process =: PID '$OLD_PID'"
+                       kill $OLD_PID
+               } || verbose_echo "old process id =: PID 'none'"
+       else
+               verbose_echo "old process id =: PID 'none'"
        fi
-
 else
        #make dir since it doesn't exist
-       mkdir /var/run/dynamic_dns
+       mkdir -p $RUNDIR
+       verbose_echo "old process id =: PID 'none'"
 fi
-echo $$ > /var/run/dynamic_dns/$service_id.pid
-
-
-
-
-#determine when the last update was
-current_time=$(monotonic_time)
-last_update=$(( $current_time - (2*$force_interval_seconds) ))
-if [ -e "/var/run/dynamic_dns/$service_id.update" ]
-then
-       last_update=$(cat /var/run/dynamic_dns/$service_id.update)
+echo $$ > $PIDFILE
+
+# determine when the last update was
+# the following lines should prevent multiple updates if hotplug fires multiple startups 
+# as described in Ticket #7820, but did not function if never an update take place
+# i.e. after a reboot (/var is linked to /tmp)
+# using uptime as reference because date might not be updated via NTP client 
+get_uptime CURR_TIME
+[ -e "$UPDFILE" ] && {
+       LAST_TIME=$(cat $UPDFILE)
+       # check also LAST > CURR because link of /var/run to /tmp might be removed
+       # i.e. boxes with larger filesystems
+       [ -z "$LAST_TIME" ] && LAST_TIME=0
+       [ $LAST_TIME -gt $CURR_TIME ] && LAST_TIME=0
+}
+if [ $LAST_TIME -eq 0 ]; then
+       verbose_echo "   last update =: never"
+else
+       EPOCH_TIME=$(( $(date +%s) - CURR_TIME + LAST_TIME ))
+       EPOCH_TIME="date -d @$EPOCH_TIME +'$DATE_FORMAT'"
+       verbose_echo "   last update =: $(eval $EPOCH_TIME)"
 fi
-time_since_update=$(($current_time - $last_update))
-
-
-human_time_since_update=$(( $time_since_update / ( 60 * 60 ) ))
-verbose_echo "time_since_update = $human_time_since_update hours"
-
-
 
-#do update and then loop endlessly, checking ip every check_interval and forcing an updating once every force_interval
-
-while [ true ]
-do
-       registered_ip=$(echo $(nslookup "$domain" 2>/dev/null) |  grep -o "Name:.*" | grep -o "$ip_regex")
-       current_ip=$(get_current_ip)
-
-
-       current_time=$(monotonic_time)
-       time_since_update=$(($current_time - $last_update))
-
-       syslog_echo "Running IP check ..."
-       verbose_echo "Running IP check..."
-       verbose_echo "current system ip = $current_ip"
-       verbose_echo "registered domain ip = $registered_ip"
-
-
-       if [ "$current_ip" != "$registered_ip" ]  || [ $force_interval_seconds -lt $time_since_update ]
-       then
-               verbose_echo "update necessary, performing update ..."
-
-               #do replacement
-               final_url=$update_url
-               for option_var in $ALL_OPTION_VARIABLES
-               do
-                       if [ "$option_var" != "update_url" ]
-                       then
-                               replace_name=$(echo "\[$option_var\]" | tr 'a-z' 'A-Z')
-                               replace_value=$(eval echo "\$$option_var")
-                               replace_value=$(echo $replace_value | sed -f /usr/lib/ddns/url_escape.sed)
-                               final_url=$(echo $final_url | sed s^"$replace_name"^"$replace_value"^g )
+# we need time here because hotplug.d is fired by netifd
+# but IP addresses are not set by DHCP/DHCPv6 etc.
+verbose_echo "       waiting =: 10 seconds for interfaces to fully come up"
+sleep 10
+
+# verify DNS server
+[ -n "$dns_server" ] && {
+       verbose_echo "******* VERIFY =: DNS server '$dns_server'"
+       verify_dns "$dns_server"
+       case $? in
+               0)      ;;      # everything OK
+               2)      critical_error "Invalid DNS server Error: '2' - nslookup can not resolve host";;
+               3)      critical_error "Invalid DNS server Error: '3' - nc (netcat) can not connect";;
+               4)      critical_error "Invalid DNS server Error: '4' - Forced IP Version don't matched";;
+               *)      critical_error "Invalid DNS server Error: '1' - unspecific error";;
+       esac
+}
+
+# verify Proxy server and set environment
+[ -n "$proxy" ] && {
+       verbose_echo "******* VERIFY =: Proxy server 'http://$proxy'"
+       verify_proxy "$proxy"
+       case $? in
+               0)      # everything OK
+                       export HTTP_PROXY="http://$proxy"
+                       export HTTPS_PROXY="http://$proxy"
+                       export http_proxy="http://$proxy"
+                       export https_proxy="http://$proxy"
+                       ;;
+               2)      critical_error "Invalid Proxy server Error: '2' - nslookup can not resolve host";;
+               3)      critical_error "Invalid Proxy server Error: '3' - nc (netcat) can not connect";;
+               4)      critical_error "Invalid Proxy server Error: '4' - Forced IP Version don't matched";;
+               5)      critical_error "Invalid Proxy server Error: '5' - proxy port missing";;
+               *)      critical_error "Invalid Proxy server Error: '1' - unspecific error";;
+       esac
+}
+
+# let's check if there is already an IP registered at the web
+# but ignore errors if not
+verbose_echo "******* DETECT =: Registered IP"
+get_registered_ip REGISTERED_IP
+
+# loop endlessly, checking ip every check_interval and forcing an updating once every force_interval
+# NEW: ### Luci Ticket 538
+# a "force_interval" of "0" will run this script only once
+# the update is only done once when an interface goes up
+# or you run /etc/init.d/ddns start or you can use a cron job
+# it will force an update without check when lastupdate happen
+# but it will verify after "check_interval" if update is seen in the web 
+# and retries on error retry_count times
+# CHANGES: ### Ticket 16363
+# modified nslookup / sed / grep to detect registered ip
+# NEW: ### Ticket 7820
+# modified nslookup to support non standard dns_server (needs to be defined in /etc/config/ddns)
+# support for BIND host command.
+# Wait for interface to fully come up, before the first update is done
+verbose_echo "*** START LOOP =: $(eval $DATE_PROG)"
+# we run NOT once
+[ $FORCE_SECONDS -gt 0 -o $VERBOSE_MODE -le 1 ] && syslog_info "Starting main loop"
+
+while : ; do
+
+       # read local IP
+       verbose_echo "******* DETECT =: Local IP"
+       get_local_ip LOCAL_IP
+       ERR_LAST=$?     # save return value
+       # Error in function
+       [ $ERR_LAST -gt 0 ] && {
+               if [ $VERBOSE_MODE -le 1 ]; then        # VERBOSE_MODE <= 1 then retry
+                       # we can't read local IP
+                       ERR_LOCAL_IP=$(( $ERR_LOCAL_IP + 1 ))
+                       [ $ERR_LOCAL_IP -gt $retry_count ] && critical_error "Can not detect local IP"
+                       verbose_echo "\n!!!!!!!!! ERROR =: detecting local IP - retry $ERR_LOCAL_IP/$retry_count in $RETRY_SECONDS seconds\n"
+                       syslog_err "Error detecting local IP - retry $ERR_LOCAL_IP/$retry_count in $RETRY_SECONDS seconds"
+                       sleep $RETRY_SECONDS
+                       continue        # jump back to the beginning of while loop
+               else
+                       verbose_echo "\n!!!!!!!!! ERROR =: detecting local IP - NO retry\n"
+               fi
+       }
+       ERR_LOCAL_IP=0  # reset err counter
+
+       # prepare update
+       # never updated or forced immediate then NEXT_TIME = 0 
+       [ $FORCE_SECONDS -eq 0 -o $LAST_TIME -eq 0 ] \
+               && NEXT_TIME=0 \
+               || NEXT_TIME=$(( $LAST_TIME + $FORCE_SECONDS ))
+       # get current uptime
+       get_uptime CURR_TIME
+       
+       # send update when current time > next time or local ip different from registered ip (as loop on error)
+       ERR_SEND=0
+       while [ $CURR_TIME -ge $NEXT_TIME -o "$LOCAL_IP" != "$REGISTERED_IP" ]; do
+               if [ $VERBOSE_MODE -gt 2 ]; then
+                       verbose_echo "  VERBOSE MODE =: NO UPDATE send to DDNS provider"
+               elif [ "$LOCAL_IP" != "$REGISTERED_IP" ]; then
+                       verbose_echo "******* UPDATE =: LOCAL: '$LOCAL_IP' <=> REGISTERED: '$REGISTERED_IP'"
+               else
+                       verbose_echo "******* FORCED =: LOCAL: '$LOCAL_IP' == REGISTERED: '$REGISTERED_IP'"
+               fi
+               # only send if VERBOSE_MODE < 3
+               ERR_LAST=0
+               [ $VERBOSE_MODE -lt 3 ] && {
+                       send_update "$LOCAL_IP" 
+                       ERR_LAST=$?     # save return value
+               }
+
+               # Error in function 
+               if [ $ERR_LAST -gt 0 ]; then
+                       if [ $VERBOSE_MODE -le 1 ]; then        # VERBOSE_MODE <=1 then retry
+                               # error sending local IP
+                               ERR_SEND=$(( $ERR_SEND + 1 ))
+                               [ $ERR_SEND -gt $retry_count ] && critical_error "can not send update to DDNS Provider"
+                               verbose_echo "\n!!!!!!!!! ERROR =: sending update - retry $ERR_SEND/$retry_count in $RETRY_SECONDS seconds\n"
+                               syslog_err "Error sending update - retry $ERR_SEND/$retry_count in $RETRY_SECONDS seconds"
+                               sleep $RETRY_SECONDS
+                               continue # re-loop
+                       else
+                               verbose_echo "\n!!!!!!!!! ERROR =: sending update to DDNS service - NO retry\n"
+                               break
                        fi
-               done
-               final_url=$(echo $final_url | sed s^"\[HTTPAUTH\]"^"${username//^/\\^}${password:+:${password//^/\\^}}"^g )
-               final_url=$(echo $final_url | sed s/"\[IP\]"/"$current_ip"/g )
-
-
-               verbose_echo "updating with url=\"$final_url\""
-
-               #here we actually connect, and perform the update
-               update_output=$( $retrieve_prog "$final_url" )
-               if [ $? -gt 0 ]
-               then
-                       syslog_echo "update failed, retrying in $retry_interval_seconds seconds"
-                       verbose_echo "update failed"
-                       sleep $retry_interval_seconds
-                       continue
+               else
+                       # we send data so save "last time"
+                       get_uptime LAST_TIME
+                       echo $LAST_TIME > $UPDFILE      # save LASTTIME to file
+                       [ "$LOCAL_IP" != "$REGISTERED_IP" ] \
+                               && syslog_notice "Changed IP: '$LOCAL_IP' successfully send" \
+                               || syslog_notice "Forced Update: IP: '$LOCAL_IP' successfully send"
+                       break # leave while
                fi
-               syslog_echo "Update successful"
-               verbose_echo "Update Output:"
-               verbose_echo "$update_output"
-               verbose_echo ""
-
-               #save the time of the update
-               current_time=$(monotonic_time)
-               last_update=$current_time
-               time_since_update='0'
-               registered_ip=$current_ip
-
-               human_time=$(date)
-               verbose_echo "update complete, time is: $human_time"
+       done
 
-               echo "$last_update" > "/var/run/dynamic_dns/$service_id.update"
-       else
-               human_time=$(date)
-               human_time_since_update=$(( $time_since_update / ( 60 * 60 ) ))
-               verbose_echo "update unnecessary"
-               verbose_echo "time since last update = $human_time_since_update hours"
-               verbose_echo "the time is now $human_time"
-       fi
+       # now we wait for check interval before testing if update was recognized
+       # only sleep if VERBOSE_MODE <= 2 because nothing send so do not wait
+       [ $VERBOSE_MODE -le 2 ] && {
+               verbose_echo "****** WAITING =: $CHECK_SECONDS seconds (Check Interval) before continue"
+               sleep $CHECK_SECONDS
+       } || verbose_echo "  VERBOSE MODE =: NO WAITING for Check Interval\n"
+
+       # read at DDNS service registered IP (in loop on error)
+       REGISTERED_IP=""
+       ERR_REG_IP=0
+       while : ; do
+               verbose_echo "******* DETECT =: Registered IP"
+               get_registered_ip REGISTERED_IP
+               ERR_LAST=$?     # save return value
+
+               # No Error in function we leave while loop
+               [ $ERR_LAST -eq 0  ] && break
+
+               # we can't read Registered IP
+               if [ $VERBOSE_MODE -le 1 ]; then        # VERBOSE_MODE <=1 then retry
+                       ERR_REG_IP=$(( $ERR_REG_IP + 1 ))
+                       [ $ERR_REG_IP -gt $retry_count ] && critical_error "can not detect registered local IP"
+                       verbose_echo "\n!!!!!!!!! ERROR =: detecting Registered IP - retry $ERR_REG_IP/$retry_count in $RETRY_SECONDS seconds\n"
+                       syslog_err "Error detecting Registered IP - retry $ERR_REG_IP/$retry_count in $RETRY_SECONDS seconds"
+                       sleep $RETRY_SECONDS
+               else
+                       verbose_echo "\n!!!!!!!!! ERROR =: detecting Registered IP - NO retry\n"
+                       break   # leave while loop
+               fi
+       done
 
-       #sleep for 10 minutes, then re-check ip && time since last update
-       sleep $check_interval_seconds
+       # IP's are still different
+       if [ "$LOCAL_IP" != "$REGISTERED_IP" ]; then
+               if [ $VERBOSE_MODE -le 1 ]; then        # VERBOSE_MODE <=1 then retry
+                       ERR_UPDATE=$(( $ERR_UPDATE + 1 ))
+                       [ $ERR_UPDATE -gt $retry_count ] && critical_error "Registered IP <> Local IP - LocalIP: '$LOCAL_IP' - RegisteredIP: '$REGISTERED_IP'"
+                       verbose_echo "\n!!!!!!!!! ERROR =: Registered IP <> Local IP - starting retry $ERR_UPDATE/$retry_count\n"
+                       syslog_warn "Warning: Registered IP <> Local IP - starting retry $ERR_UPDATE/$retry_count"
+                       continue # loop to beginning
+               else
+                       verbose_echo "\n!!!!!!!!! ERROR =: Registered IP <> Local IP - LocalIP: '$LOCAL_IP' - RegisteredIP: '$REGISTERED_IP' - NO retry\n"
+               fi
+       fi              
+
+       # we checked successful the last update
+       ERR_UPDATE=0                    # reset error counter
+
+       # force_update=0 or VERBOSE_MODE > 1 - leave the main loop
+       [ $FORCE_SECONDS -eq 0 -o $VERBOSE_MODE -gt 1 ] && {
+               verbose_echo "****** LEAVING =: $(eval $DATE_PROG)"
+               syslog_info "Leaving"
+               break
+       }
+       verbose_echo "********* LOOP =: $(eval $DATE_PROG)"
+       syslog_info "Rerun IP check"
 done
 
-#should never get here since we're a daemon, but I'll throw it in anyway
-return 0
-
-
-
+verbose_echo "****** STOPPED =: PID '$$' at $(eval $DATE_PROG)\n"
+syslog_info "Done"
 
+exit 0