Ticket #12500: 20141002-dynamic_dns_updater.sh

File 20141002-dynamic_dns_updater.sh, 17.3 KB (added by chris5560, 3 years ago)
Line 
1#!/bin/sh
2# /usr/lib/ddns/dynamic_dns_updater.sh
3#
4# Original written by Eric Paul Bishop, January 2008
5# Distributed under the terms of the GNU General Public License (GPL) version 2.0
6# (Loosely) based on the script on the one posted by exobyte in the forums here:
7# http://forum.openwrt.org/viewtopic.php?id=14040
8#
9# extended and partial rewritten by Christian Schoenebeck in August 2014 to support:
10# - IPv6 DDNS services
11# - DNS Server to retrieve registered IP including TCP transport
12# - Proxy Server to send out updates
13# - force_interval=0 to run once
14# - the usage of BIND's host command instead of BusyBox's nslookup if installed
15# - extended Verbose Mode and log file support for better error detection
16#
17# variables in small chars are read from /etc/config/ddns
18# variables in big chars are defined inside these scripts as global vars
19# variables in big chars beginning with "__" are local defined inside functions only
20#set -vx        #script debugger
21
22[ $# -lt 1 -o -n "${2//[0-3]/}" -o ${#2} -gt 1 ] && {
23        echo -e "\n  USAGE:"
24        echo -e "  $0 [SECTION] [VERBOSE_MODE]\n"
25        echo    "  [SECTION]      - service section as defined in /etc/config/ddns"
26        echo    "  [VERBOSE_MODE] - '0' NO output to console"
27        echo    "                   '1' output to console"
28        echo    "                   '2' output to console AND logfile"
29        echo    "                       + run once WITHOUT retry on error"
30        echo    "                   '3' output to console AND logfile"
31        echo    "                       + run once WITHOUT retry on error"
32        echo -e "                       + NOT sending update to DDNS service\n"
33        exit 1
34}
35
36. /usr/lib/ddns/dynamic_dns_functions.sh        # global vars are also defined here
37
38SECTION_ID="$1"
39VERBOSE_MODE=${2:-1}    #default mode is log to console
40
41# set file names
42PIDFILE="$RUNDIR/$SECTION_ID.pid"       # Process ID file
43UPDFILE="$RUNDIR/$SECTION_ID.update"    # last update successful send (system uptime)
44LOGFILE="$LOGDIR/$SECTION_ID.log"       # log file
45
46# VERBOSE_MODE > 1 delete logfile if exist to create an empty one
47# only with this data of this run for easier diagnostic
48# new one created by verbose_echo function
49[ $VERBOSE_MODE -gt 1 -a -f $LOGFILE ] && rm -f $LOGFILE
50
51################################################################################
52# Leave this comment here, to clearly document variable names that are expected/possible
53# Use load_all_config_options to load config options, which is a much more flexible solution.
54#
55# config_load "ddns"
56# config_get <variable> $SECTION_ID <option]>
57#
58# defined options (also used as variable):
59#
60# enable        self-explanatory
61# interface     network interface used by hotplug.d i.e. 'wan' or 'wan6'
62#
63# service_name  Which DDNS service do you use or "custom"
64# update_url    URL to use to update your "custom" DDNS service
65#
66# domain        Your DNS name / replace [DOMAIN] in update_url
67# username      Username of your DDNS service account / replace [USERNAME] in update_url
68# password      Password of your DDNS service account / replace [PASSWORD] in update_url
69#
70# use_https     use HTTPS to update DDNS service
71# cacert        file or directory where HTTPS can find certificates to verify server; 'IGNORE' ignore check of server certificate
72#
73# use_syslog    log activity to syslog
74#
75# ip_source     source to detect current local IP ('network' or 'web' or 'script' or 'interface')
76# ip_network    local defined network to read IP from i.e. 'wan' or 'wan6'
77# ip_url        URL to read local address from i.e. http://checkip.dyndns.com/ or http://checkipv6.dyndns.com/
78# ip_script     full path and name of your script to detect local IP
79# ip_interface  physical interface to use for detecting
80#
81# check_interval        check for changes every  !!! checks below 10 minutes make no sense because the Internet
82# check_unit            'days' 'hours' 'minutes' !!! needs about 5-10 minutes to sync an IP-change for an DNS entry
83#
84# force_interval        force to send an update to your service if no change was detected
85# force_unit            'days' 'hours' 'minutes' !!! force_interval="0" runs this script once for use i.e. with cron
86#
87# retry_interval        if error was detected retry in
88# retry_unit            'days' 'hours' 'minutes' 'seconds'
89# retry_count           #NEW# number of retries before scripts stops
90#
91# use_ipv6              #NEW# detecting/sending IPv6 address
92# force_ipversion       #NEW# force usage of IPv4 or IPv6 for the whole detection and update communication
93# dns_server            #NEW# using a non default dns server to get Registered IP from Internet
94# force_dnstcp          #NEW# force communication with DNS server via TCP instead of default UDP
95# proxy                 #NEW# using a proxy for communication !!! ALSO used to detect local IP via web => return proxy's IP !!!
96# use_logfile           #NEW# self-explanatory "/var/log/ddns/$SECTION_ID.log"
97#
98# some functionality needs
99# - GNU Wget or cURL installed for sending updates to DDNS service
100# - BIND host installed to detect Registered IP
101#
102################################################################################
103
104# verify and load SECTION_ID is exists
105[ "$(uci_get ddns $SECTION_ID)" != "service" ] && {
106        [ $VERBOSE_MODE -le 1 ] && VERBOSE_MODE=2       # force console out and logfile output
107        [ -f $LOGFILE ] && rm -f $LOGFILE               # clear logfile before first entry
108        verbose_echo "\n ************** =: ************** ************** **************"
109        verbose_echo "       STARTED =: PID '$$' at $(eval $DATE_PROG)"
110        verbose_echo "    UCI CONFIG =:\n$(uci -q show ddns | grep '=service' | sort)"
111        critical_error "Service '$SECTION_ID' not defined"
112}
113load_all_config_options "ddns" "$SECTION_ID"
114
115verbose_echo "\n ************** =: ************** ************** **************"
116verbose_echo "       STARTED =: PID '$$' at $(eval $DATE_PROG)"
117syslog_info "Started"
118case $VERBOSE_MODE in
119        0) verbose_echo "  verbose mode =: '0' - run normal, NO console output";;
120        1) verbose_echo "  verbose mode =: '1' - run normal, console mode";;
121        2) verbose_echo "  verbose mode =: '2' - run once, NO retry on error";;
122        3) verbose_echo "  verbose mode =: '3' - run once, NO retry on error, NOT sending update";;
123        *) critical_error "ERROR detecting VERBOSE_MODE '$VERBOSE_MODE'"
124esac
125verbose_echo "    UCI CONFIG =:\n$(uci -q show ddns.$SECTION_ID | sort)"
126
127# set defaults if not defined
128[ -z "$enabled" ]         && enabled=0
129[ -z "$retry_count" ]     && retry_count=5
130[ -z "$use_syslog" ]      && use_syslog=0       # not use syslog
131[ -z "$use_https" ]       && use_https=0        # not use https
132[ -z "$use_logfile" ]     && use_logfile=1      # NEW - use logfile by default
133[ -z "$use_ipv6" ]        && use_ipv6=0         # NEW - use IPv4 by default
134[ -z "$force_ipversion" ] && force_ipversion=# NEW - default let system decide
135[ -z "$force_dnstcp" ]    && force_dnstcp=0     # NEW - default UDP
136[ -z "$ip_source" ]       && ip_source="network"
137[ "$ip_source" = "network" -a -z "$ip_network" -a $use_ipv6 -eq 0 ] && ip_network="wan"  # IPv4: default wan
138[ "$ip_source" = "network" -a -z "$ip_network" -a $use_ipv6 -eq 1 ] && ip_network="wan6" # IPv6: default wan6
139[ "$ip_source" = "web" -a -z "$ip_url" -a $use_ipv6 -eq 0 ] && ip_url="http://checkip.dyndns.com"
140[ "$ip_source" = "web" -a -z "$ip_url" -a $use_ipv6 -eq 1 ] && ip_url="http://checkipv6.dyndns.com"
141[ "$ip_source" = "interface" -a -z "$ip_interface" ] && ip_interface="eth1"
142
143# check configuration and enabled state
144[ -z "$domain" -o -z "$username" -o -z "$password" ] && critical_error "Service Configuration not correctly configured"
145[ $enabled -eq 0 ] && critical_error "Service Configuration is disabled"
146
147# verify script if configured and executable
148if [ "$ip_source" = "script" ]; then
149        [ -z "$ip_script" ] && critical_error "No script defined to detect local IP"
150        [ -x "$ip_script" ] || critical_error "Script to detect local IP not found or not executable"
151fi
152
153# compute update interval in seconds
154get_seconds CHECK_SECONDS ${check_interval:-10} ${check_unit:-"minutes"} # default 10 min
155get_seconds FORCE_SECONDS ${force_interval:-72} ${force_unit:-"hours"}   # default 3 days
156get_seconds RETRY_SECONDS ${retry_interval:-60} ${retry_unit:-"seconds"} # default 60 sec
157verbose_echo "check interval =: $CHECK_SECONDS seconds"
158verbose_echo "force interval =: $FORCE_SECONDS seconds"
159verbose_echo "retry interval =: $RETRY_SECONDS seconds"
160verbose_echo " retry counter =: $retry_count times"
161
162# determine what update url we're using if a service_name is supplied
163# otherwise update_url is set inside configuration (custom service)
164# or update_script is set inside configuration (custom update script)
165[ -n "$service_name" ] && get_service_url update_url
166[ -z "$update_url" -a -z "$update_script" ] && critical_error "no update_url found/defined or no custom update_script defined"
167[ -n "$update_script" -a ! -f "$update_script" ] && critical_error "custom update_script not found"
168
169#kill old process if it exists & set new pid file
170if [ -d $RUNDIR ]; then
171        #if process is already running, stop it
172        if [ -e "$PIDFILE" ]; then
173                OLD_PID=$(cat $PIDFILE)
174                ps | grep -q "^[\t ]*$OLD_PID" && {
175                        verbose_echo "   old process =: PID '$OLD_PID'"
176                        kill $OLD_PID
177                } || verbose_echo "old process id =: PID 'none'"
178        else
179                verbose_echo "old process id =: PID 'none'"
180        fi
181else
182        #make dir since it doesn't exist
183        mkdir -p $RUNDIR
184        verbose_echo "old process id =: PID 'none'"
185fi
186echo $$ > $PIDFILE
187
188# determine when the last update was
189# the following lines should prevent multiple updates if hotplug fires multiple startups
190# as described in Ticket #7820, but did not function if never an update take place
191# i.e. after a reboot (/var is linked to /tmp)
192# using uptime as reference because date might not be updated via NTP client
193get_uptime CURR_TIME
194[ -e "$UPDFILE" ] && {
195        LAST_TIME=$(cat $UPDFILE)
196        # check also LAST > CURR because link of /var/run to /tmp might be removed
197        # i.e. boxes with larger filesystems
198        [ -z "$LAST_TIME" ] && LAST_TIME=0
199        [ $LAST_TIME -gt $CURR_TIME ] && LAST_TIME=0
200}
201if [ $LAST_TIME -eq 0 ]; then
202        verbose_echo "   last update =: never"
203else
204        EPOCH_TIME=$(( $(date +%s) - CURR_TIME + LAST_TIME ))
205        EPOCH_TIME="date -d @$EPOCH_TIME +'$DATE_FORMAT'"
206        verbose_echo "   last update =: $(eval $EPOCH_TIME)"
207fi
208
209# we need time here because hotplug.d is fired by netifd
210# but IP addresses are not set by DHCP/DHCPv6 etc.
211verbose_echo "       waiting =: 10 seconds for interfaces to fully come up"
212sleep 10
213
214# verify DNS server
215[ -n "$dns_server" ] && {
216        verbose_echo "******* VERIFY =: DNS server '$dns_server'"
217        verify_dns "$dns_server"
218        case $? in
219                0)      ;;      # everything OK
220                2)      critical_error "Invalid DNS server Error: '2' - nslookup can not resolve host";;
221                3)      critical_error "Invalid DNS server Error: '3' - nc (netcat) can not connect";;
222                4)      critical_error "Invalid DNS server Error: '4' - Forced IP Version don't matched";;
223                *)      critical_error "Invalid DNS server Error: '1' - unspecific error";;
224        esac
225}
226
227# verify Proxy server and set environment
228[ -n "$proxy" ] && {
229        verbose_echo "******* VERIFY =: Proxy server 'http://$proxy'"
230        verify_proxy "$proxy"
231        case $? in
232                0)      # everything OK
233                        export HTTP_PROXY="http://$proxy"
234                        export HTTPS_PROXY="http://$proxy"
235                        export http_proxy="http://$proxy"
236                        export https_proxy="http://$proxy"
237                        ;;
238                2)      critical_error "Invalid Proxy server Error: '2' - nslookup can not resolve host";;
239                3)      critical_error "Invalid Proxy server Error: '3' - nc (netcat) can not connect";;
240                4)      critical_error "Invalid Proxy server Error: '4' - Forced IP Version don't matched";;
241                5)      critical_error "Invalid Proxy server Error: '5' - proxy port missing";;
242                *)      critical_error "Invalid Proxy server Error: '1' - unspecific error";;
243        esac
244}
245
246# let's check if there is already an IP registered at the web
247# but ignore errors if not
248verbose_echo "******* DETECT =: Registered IP"
249get_registered_ip REGISTERED_IP
250
251# loop endlessly, checking ip every check_interval and forcing an updating once every force_interval
252# NEW: ### Luci Ticket 538
253# a "force_interval" of "0" will run this script only once
254# the update is only done once when an interface goes up
255# or you run /etc/init.d/ddns start or you can use a cron job
256# it will force an update without check when lastupdate happen
257# but it will verify after "check_interval" if update is seen in the web
258# and retries on error retry_count times
259# CHANGES: ### Ticket 16363
260# modified nslookup / sed / grep to detect registered ip
261# NEW: ### Ticket 7820
262# modified nslookup to support non standard dns_server (needs to be defined in /etc/config/ddns)
263# support for BIND host command.
264# Wait for interface to fully come up, before the first update is done
265verbose_echo "*** START LOOP =: $(eval $DATE_PROG)"
266# we run NOT once
267[ $FORCE_SECONDS -gt 0 -o $VERBOSE_MODE -le 1 ] && syslog_info "Starting main loop"
268
269while : ; do
270
271        # read local IP
272        verbose_echo "******* DETECT =: Local IP"
273        get_local_ip LOCAL_IP
274        ERR_LAST=$?     # save return value
275        # Error in function
276        [ $ERR_LAST -gt 0 ] && {
277                if [ $VERBOSE_MODE -le 1 ]; then        # VERBOSE_MODE <= 1 then retry
278                        # we can't read local IP
279                        ERR_LOCAL_IP=$(( $ERR_LOCAL_IP + 1 ))
280                        [ $ERR_LOCAL_IP -gt $retry_count ] && critical_error "Can not detect local IP"
281                        verbose_echo "\n!!!!!!!!! ERROR =: detecting local IP - retry $ERR_LOCAL_IP/$retry_count in $RETRY_SECONDS seconds\n"
282                        syslog_err "Error detecting local IP - retry $ERR_LOCAL_IP/$retry_count in $RETRY_SECONDS seconds"
283                        sleep $RETRY_SECONDS
284                        continue        # jump back to the beginning of while loop
285                else
286                        verbose_echo "\n!!!!!!!!! ERROR =: detecting local IP - NO retry\n"
287                fi
288        }
289        ERR_LOCAL_IP=# reset err counter
290
291        # prepare update
292        # never updated or forced immediate then NEXT_TIME = 0
293        [ $FORCE_SECONDS -eq 0 -o $LAST_TIME -eq 0 ] \
294                && NEXT_TIME=0 \
295                || NEXT_TIME=$(( $LAST_TIME + $FORCE_SECONDS ))
296        # get current uptime
297        get_uptime CURR_TIME
298       
299        # send update when current time > next time or local ip different from registered ip (as loop on error)
300        ERR_SEND=0
301        while [ $CURR_TIME -ge $NEXT_TIME -o "$LOCAL_IP" != "$REGISTERED_IP" ]; do
302                if [ $VERBOSE_MODE -gt 2 ]; then
303                        verbose_echo "  VERBOSE MODE =: NO UPDATE send to DDNS provider"
304                elif [ "$LOCAL_IP" != "$REGISTERED_IP" ]; then
305                        verbose_echo "******* UPDATE =: LOCAL: '$LOCAL_IP' <=> REGISTERED: '$REGISTERED_IP'"
306                else
307                        verbose_echo "******* FORCED =: LOCAL: '$LOCAL_IP' == REGISTERED: '$REGISTERED_IP'"
308                fi
309                # only send if VERBOSE_MODE < 3
310                ERR_LAST=0
311                [ $VERBOSE_MODE -lt 3 ] && {
312                        send_update "$LOCAL_IP" 
313                        ERR_LAST=$?     # save return value
314                }
315
316                # Error in function
317                if [ $ERR_LAST -gt 0 ]; then
318                        if [ $VERBOSE_MODE -le 1 ]; then        # VERBOSE_MODE <=1 then retry
319                                # error sending local IP
320                                ERR_SEND=$(( $ERR_SEND + 1 ))
321                                [ $ERR_SEND -gt $retry_count ] && critical_error "can not send update to DDNS Provider"
322                                verbose_echo "\n!!!!!!!!! ERROR =: sending update - retry $ERR_SEND/$retry_count in $RETRY_SECONDS seconds\n"
323                                syslog_err "Error sending update - retry $ERR_SEND/$retry_count in $RETRY_SECONDS seconds"
324                                sleep $RETRY_SECONDS
325                                continue # re-loop
326                        else
327                                verbose_echo "\n!!!!!!!!! ERROR =: sending update to DDNS service - NO retry\n"
328                                break
329                        fi
330                else
331                        # we send data so save "last time"
332                        get_uptime LAST_TIME
333                        echo $LAST_TIME > $UPDFILE      # save LASTTIME to file
334                        [ "$LOCAL_IP" != "$REGISTERED_IP" ] \
335                                && syslog_notice "Changed IP: '$LOCAL_IP' successfully send" \
336                                || syslog_notice "Forced Update: IP: '$LOCAL_IP' successfully send"
337                        break # leave while
338                fi
339        done
340
341        # now we wait for check interval before testing if update was recognized
342        # only sleep if VERBOSE_MODE <= 2 because nothing send so do not wait
343        [ $VERBOSE_MODE -le 2 ] && {
344                verbose_echo "****** WAITING =: $CHECK_SECONDS seconds (Check Interval) before continue"
345                sleep $CHECK_SECONDS
346        } || verbose_echo "  VERBOSE MODE =: NO WAITING for Check Interval\n"
347
348        # read at DDNS service registered IP (in loop on error)
349        REGISTERED_IP=""
350        ERR_REG_IP=0
351        while : ; do
352                verbose_echo "******* DETECT =: Registered IP"
353                get_registered_ip REGISTERED_IP
354                ERR_LAST=$?     # save return value
355
356                # No Error in function we leave while loop
357                [ $ERR_LAST -eq 0  ] && break
358
359                # we can't read Registered IP
360                if [ $VERBOSE_MODE -le 1 ]; then        # VERBOSE_MODE <=1 then retry
361                        ERR_REG_IP=$(( $ERR_REG_IP + 1 ))
362                        [ $ERR_REG_IP -gt $retry_count ] && critical_error "can not detect registered local IP"
363                        verbose_echo "\n!!!!!!!!! ERROR =: detecting Registered IP - retry $ERR_REG_IP/$retry_count in $RETRY_SECONDS seconds\n"
364                        syslog_err "Error detecting Registered IP - retry $ERR_REG_IP/$retry_count in $RETRY_SECONDS seconds"
365                        sleep $RETRY_SECONDS
366                else
367                        verbose_echo "\n!!!!!!!!! ERROR =: detecting Registered IP - NO retry\n"
368                        break   # leave while loop
369                fi
370        done
371
372        # IP's are still different
373        if [ "$LOCAL_IP" != "$REGISTERED_IP" ]; then
374                if [ $VERBOSE_MODE -le 1 ]; then        # VERBOSE_MODE <=1 then retry
375                        ERR_UPDATE=$(( $ERR_UPDATE + 1 ))
376                        [ $ERR_UPDATE -gt $retry_count ] && critical_error "Registered IP <> Local IP - LocalIP: '$LOCAL_IP' - RegisteredIP: '$REGISTERED_IP'"
377                        verbose_echo "\n!!!!!!!!! ERROR =: Registered IP <> Local IP - starting retry $ERR_UPDATE/$retry_count\n"
378                        syslog_warn "Warning: Registered IP <> Local IP - starting retry $ERR_UPDATE/$retry_count"
379                        continue # loop to beginning
380                else
381                        verbose_echo "\n!!!!!!!!! ERROR =: Registered IP <> Local IP - LocalIP: '$LOCAL_IP' - RegisteredIP: '$REGISTERED_IP' - NO retry\n"
382                fi
383        fi             
384
385        # we checked successful the last update
386        ERR_UPDATE=0                    # reset error counter
387
388        # force_update=0 or VERBOSE_MODE > 1 - leave the main loop
389        [ $FORCE_SECONDS -eq 0 -o $VERBOSE_MODE -gt 1 ] && {
390                verbose_echo "****** LEAVING =: $(eval $DATE_PROG)"
391                syslog_info "Leaving"
392                break
393        }
394        verbose_echo "********* LOOP =: $(eval $DATE_PROG)"
395        syslog_info "Rerun IP check"
396done
397
398verbose_echo "****** STOPPED =: PID '$$' at $(eval $DATE_PROG)\n"
399syslog_info "Done"
400
401exit 0