Assuming the following dns zone configuration for kresin.me: . 1800 IN NS ns.inwx.de. home.kresin.me. 1800 IN NS gw.home.kresin.me. gw.home.kresin.me. 60 IN A 198.51.100.1 Using the example, gw.home.kresin.me is a glue record and used as DDNS record as well. gw.home.kresin.me points to the OpenWrt installation where the ddns-scripts are running. Since ns.inwx.de isn't the authoritative server for home.kresin.me, a query for the the A record of gw.home.kresin.me will be "forwarded" to and replied by the authoritative server 198.51.100.1. If the authoritative server can't be reached, the query times out without any address returned. The only way to get the A record of the glue record is to use bind host in verbose output mode, query ns.inwx.de for gw.home.kresin.me and evaluate the additional section of the response. To my knowledge, only bind host in verbose output mode shows the additional section of the response. Therefore, this features depends on bind host. It is not possible to use the bind host verbose mode for all queries. In verbose mode the "has address" line isn't returned. Grepping for the hostname might fail if the hostname is CNAME. Grepping for the first occurrence of an ip-address is way to error prone, since it could return the ip address of a different record type. Signed-off-by: Mathias Kresin <dev@kresin.me>
360 lines
16 KiB
Bash
Executable file
360 lines
16 KiB
Bash
Executable file
#!/bin/sh
|
|
# /usr/lib/ddns/dynamic_dns_updater.sh
|
|
#
|
|
#.Distributed under the terms of the GNU General Public License (GPL) version 2.0
|
|
# Original written by Eric Paul Bishop, January 2008
|
|
# (Loosely) based on the script on the one posted by exobyte in the forums here:
|
|
# http://forum.openwrt.org/viewtopic.php?id=14040
|
|
# extended and partial rewritten
|
|
#.2014-2016 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
|
|
#
|
|
# 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
|
|
|
|
. /usr/lib/ddns/dynamic_dns_functions.sh # global vars are also defined here
|
|
|
|
[ $# -lt 1 -o -n "${2//[0-3]/}" -o ${#2} -gt 1 ] && {
|
|
echo -e "\n ddns-scripts Version: $VERSION"
|
|
echo -e "\n USAGE:"
|
|
echo " $0 [OPTION]"
|
|
echo " [OPTION] '-V' or '--version' display version and exit"
|
|
echo -e "\n $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
|
|
}
|
|
|
|
[ "$1" = "-V" -o "$1" = "--version" ] && {
|
|
echo -e "ddns-scripts $VERSION\n"
|
|
exit 0
|
|
}
|
|
|
|
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)
|
|
DATFILE="$RUNDIR/$SECTION_ID.dat" # save stdout data of WGet and other extern programs called
|
|
ERRFILE="$RUNDIR/$SECTION_ID.err" # save stderr output of WGet and other extern programs called
|
|
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 write_log function
|
|
[ $VERBOSE_MODE -gt 1 -a -f $LOGFILE ] && rm -f $LOGFILE
|
|
|
|
# TRAP handler
|
|
trap "trap_handler 0 \$?" 0 # handle script exit with exit status
|
|
trap "trap_handler 1" 1 # SIGHUP Hangup / reload config
|
|
trap "trap_handler 2" 2 # SIGINT Terminal interrupt
|
|
trap "trap_handler 3" 3 # SIGQUIT Terminal quit
|
|
# trap "trap_handler 9" 9 # SIGKILL no chance to trap
|
|
trap "trap_handler 15" 15 # SIGTERM Termination
|
|
|
|
################################################################################
|
|
# 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>
|
|
#
|
|
# defined options (also used as variable):
|
|
#
|
|
# enable self-explanatory
|
|
# interface network interface used by hotplug.d i.e. 'wan' or 'wan6'
|
|
#
|
|
# service_name Which DDNS service do you use or "custom"
|
|
# update_url URL to use to update your "custom" DDNS service
|
|
# update_script SCRIPT to use to update your "custom" DDNS service
|
|
#
|
|
# lookup_host FQDN of ONE of your at DDNS service defined host / required to validate if IP update happen/necessary
|
|
# domain Nomally your DDNS hostname / replace [DOMAIN] in update_url
|
|
# username Username of your DDNS service account / urlenceded and replace [USERNAME] in update_url
|
|
# password Password of your DDNS service account / urlencoded and replace [PASSWORD] in update_url
|
|
# param_enc Optional parameter for (later) usage / urlencoded and replace [PARAMENC] in update_url
|
|
# param_opt Optional parameter for (later) usage / replace [PARAMOPT] in update_url
|
|
#
|
|
# 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
|
|
#
|
|
# 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
|
|
#
|
|
# 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
|
|
#
|
|
# 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
|
|
#
|
|
# retry_interval if error was detected retry in
|
|
# retry_unit 'days' 'hours' 'minutes' 'seconds'
|
|
# retry_count number of retries before scripts stops
|
|
#
|
|
# use_ipv6 detecting/sending IPv6 address
|
|
# force_ipversion force usage of IPv4 or IPv6 for the whole detection and update communication
|
|
# dns_server using a non default dns server to get Registered IP from Internet
|
|
# force_dnstcp force communication with DNS server via TCP instead of default UDP
|
|
# proxy using a proxy for communication !!! ALSO used to detect local IP via web => return proxy's IP !!!
|
|
# use_logfile self-explanatory "/var/log/ddns/$SECTION_ID.log"
|
|
# is_glue the record that should be updated is a glue record
|
|
#
|
|
# some functionality needs
|
|
# - GNU Wget or cURL installed for sending updates to DDNS service
|
|
# - BIND host installed to detect Registered IP
|
|
#
|
|
################################################################################
|
|
|
|
load_all_config_options "ddns" "$SECTION_ID"
|
|
ERR_LAST=$? # save return code - equal 0 if SECTION_ID found
|
|
|
|
# set defaults if not defined
|
|
[ -z "$enabled" ] && enabled=0
|
|
[ -z "$retry_count" ] && retry_count=0 # endless retry
|
|
[ -z "$use_syslog" ] && use_syslog=2 # syslog "Notice"
|
|
[ -z "$use_https" ] && use_https=0 # not use https
|
|
[ -z "$use_logfile" ] && use_logfile=1 # use logfile by default
|
|
[ -z "$use_ipv6" ] && use_ipv6=0 # use IPv4 by default
|
|
[ -z "$force_ipversion" ] && force_ipversion=0 # default let system decide
|
|
[ -z "$force_dnstcp" ] && force_dnstcp=0 # 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"
|
|
|
|
# SECTION_ID does not exists
|
|
[ $ERR_LAST -ne 0 ] && {
|
|
[ $VERBOSE_MODE -le 1 ] && VERBOSE_MODE=2 # force console out and logfile output
|
|
[ -f $LOGFILE ] && rm -f $LOGFILE # clear logfile before first entry
|
|
write_log 7 "************ ************** ************** **************"
|
|
write_log 5 "PID '$$' started at $(eval $DATE_PROG)"
|
|
write_log 7 "ddns version : $VERSION"
|
|
write_log 7 "uci configuration:\n$(uci -q show ddns | grep '=service' | sort)"
|
|
write_log 14 "Service section '$SECTION_ID' not defined"
|
|
}
|
|
|
|
write_log 7 "************ ************** ************** **************"
|
|
write_log 5 "PID '$$' started at $(eval $DATE_PROG)"
|
|
write_log 7 "ddns version : $VERSION"
|
|
write_log 7 "uci configuration:\n$(uci -q show ddns.$SECTION_ID | sort)"
|
|
# write_log 7 "ddns version : $(opkg list-installed ddns-scripts | cut -d ' ' -f 3)"
|
|
case $VERBOSE_MODE in
|
|
0) write_log 7 "verbose mode : 0 - run normal, NO console output";;
|
|
1) write_log 7 "verbose mode : 1 - run normal, console mode";;
|
|
2) write_log 7 "verbose mode : 2 - run once, NO retry on error";;
|
|
3) write_log 7 "verbose mode : 3 - run once, NO retry on error, NOT sending update";;
|
|
*) write_log 14 "error detecting VERBOSE_MODE '$VERBOSE_MODE'";;
|
|
esac
|
|
|
|
# check enabled state otherwise we don't need to continue
|
|
[ $enabled -eq 0 ] && write_log 14 "Service section disabled!"
|
|
|
|
# determine what update url we're using if a service_name is supplied
|
|
# otherwise update_url is set inside configuration (custom update url)
|
|
# or update_script is set inside configuration (custom update script)
|
|
[ -n "$service_name" ] && get_service_data update_url update_script SRV_ANSWER
|
|
[ -z "$update_url" -a -z "$update_script" ] && write_log 14 "No update_url found/defined or no update_script found/defined!"
|
|
[ -n "$update_script" -a ! -f "$update_script" ] && write_log 14 "Custom update_script not found!"
|
|
|
|
# temporary needed to convert existing uci settings
|
|
[ -z "$lookup_host" ] && {
|
|
uci -q set ddns.$SECTION_ID.lookup_host="$domain"
|
|
uci -q commit ddns
|
|
lookup_host="$domain"
|
|
}
|
|
# later versions only check if configured correctly
|
|
|
|
# without lookup host and possibly other required options we can do nothing for you
|
|
[ -z "$lookup_host" ] && write_log 14 "Service section not configured correctly! Missing 'lookup_host'"
|
|
|
|
[ -n "$update_url" ] && {
|
|
# only check if update_url is given, update_scripts have to check themselves
|
|
[ -z "$domain" ] && $(echo "$update_url" | grep "\[DOMAIN\]" >/dev/null 2>&1) && \
|
|
write_log 14 "Service section not configured correctly! Missing 'domain'"
|
|
[ -z "$username" ] && $(echo "$update_url" | grep "\[USERNAME\]" >/dev/null 2>&1) && \
|
|
write_log 14 "Service section not configured correctly! Missing 'username'"
|
|
[ -z "$password" ] && $(echo "$update_url" | grep "\[PASSWORD\]" >/dev/null 2>&1) && \
|
|
write_log 14 "Service section not configured correctly! Missing 'password'"
|
|
[ -z "$param_enc" ] && $(echo "$update_url" | grep "\[PARAMENC\]" >/dev/null 2>&1) && \
|
|
write_log 14 "Service section not configured correctly! Missing 'param_enc'"
|
|
[ -z "$param_opt" ] && $(echo "$update_url" | grep "\[PARAMOPT\]" >/dev/null 2>&1) && \
|
|
write_log 14 "Service section not configured correctly! Missing 'param_opt'"
|
|
}
|
|
|
|
# url encode username (might be email or something like this)
|
|
# and password (might have special chars for security reason)
|
|
# and optional parameter "param_enc"
|
|
[ -n "$username" ] && urlencode URL_USER "$username"
|
|
[ -n "$password" ] && urlencode URL_PASS "$password"
|
|
[ -n "$param_enc" ] && urlencode URL_PENC "$param_enc"
|
|
|
|
# verify ip_source 'script' if script is configured and executable
|
|
if [ "$ip_source" = "script" ]; then
|
|
set -- $ip_script #handling script with parameters, we need a trick
|
|
[ -z "$1" ] && write_log 14 "No script defined to detect local IP!"
|
|
[ -x "$1" ] || write_log 14 "Script to detect local IP 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
|
|
[ $CHECK_SECONDS -lt 300 ] && CHECK_SECONDS=300 # minimum 5 minutes
|
|
[ $FORCE_SECONDS -gt 0 -a $FORCE_SECONDS -lt $CHECK_SECONDS ] && FORCE_SECONDS=$CHECK_SECONDS # FORCE_SECONDS >= CHECK_SECONDS or 0
|
|
write_log 7 "check interval: $CHECK_SECONDS seconds"
|
|
write_log 7 "force interval: $FORCE_SECONDS seconds"
|
|
write_log 7 "retry interval: $RETRY_SECONDS seconds"
|
|
write_log 7 "retry counter : $retry_count times"
|
|
|
|
# kill old process if it exists & set new pid file
|
|
stop_section_processes "$SECTION_ID"
|
|
[ $? -gt 0 ] && write_log 7 "'SIGTERM' was send to old process" || write_log 7 "No old process"
|
|
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
|
|
write_log 7 "last update: never"
|
|
else
|
|
EPOCH_TIME=$(( $(date +%s) - CURR_TIME + LAST_TIME ))
|
|
EPOCH_TIME="date -d @$EPOCH_TIME +'$DATE_FORMAT'"
|
|
write_log 7 "last update: $(eval $EPOCH_TIME)"
|
|
fi
|
|
|
|
# verify DNS server
|
|
[ -n "$dns_server" ] && verify_dns "$dns_server"
|
|
|
|
# verify Proxy server and set environment
|
|
[ -n "$proxy" ] && {
|
|
verify_proxy "$proxy" && {
|
|
# everything ok set proxy
|
|
export HTTP_PROXY="http://$proxy"
|
|
export HTTPS_PROXY="http://$proxy"
|
|
export http_proxy="http://$proxy"
|
|
export https_proxy="http://$proxy"
|
|
}
|
|
}
|
|
|
|
# let's check if there is already an IP registered on the web
|
|
get_registered_ip REGISTERED_IP "NO_RETRY"
|
|
ERR_LAST=$?
|
|
# No error or No IP set otherwise retry
|
|
[ $ERR_LAST -eq 0 -o $ERR_LAST -eq 127 ] || get_registered_ip REGISTERED_IP
|
|
# on IPv6 we use expanded version to be shure when comparing
|
|
[ $use_ipv6 -eq 1 ] && expand_ipv6 "$REGISTERED_IP" REGISTERED_IP
|
|
|
|
# loop endlessly, checking ip every check_interval and forcing an updating once every force_interval
|
|
write_log 6 "Starting main loop at $(eval $DATE_PROG)"
|
|
while : ; do
|
|
|
|
get_local_ip LOCAL_IP # read local IP
|
|
[ $use_ipv6 -eq 1 ] && expand_ipv6 "$LOCAL_IP" LOCAL_IP # on IPv6 we use expanded version
|
|
|
|
# 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_uptime CURR_TIME # get current uptime
|
|
|
|
# send update when current time > next time or local ip different from registered ip
|
|
if [ $CURR_TIME -ge $NEXT_TIME -o "$LOCAL_IP" != "$REGISTERED_IP" ]; then
|
|
if [ $VERBOSE_MODE -gt 2 ]; then
|
|
write_log 7 "Verbose Mode: $VERBOSE_MODE - NO UPDATE send"
|
|
elif [ "$LOCAL_IP" != "$REGISTERED_IP" ]; then
|
|
write_log 7 "Update needed - L: '$LOCAL_IP' <> R: '$REGISTERED_IP'"
|
|
else
|
|
write_log 7 "Forced Update - L: '$LOCAL_IP' == R: '$REGISTERED_IP'"
|
|
fi
|
|
|
|
ERR_LAST=0
|
|
[ $VERBOSE_MODE -lt 3 ] && {
|
|
# only send if VERBOSE_MODE < 3
|
|
send_update "$LOCAL_IP"
|
|
ERR_LAST=$? # save return value
|
|
}
|
|
|
|
# error sending local IP to provider
|
|
# we have no communication error (handled inside send_update/do_transfer)
|
|
# but update was not recognized
|
|
# do NOT retry after RETRY_SECONDS, do retry after CHECK_SECONDS
|
|
# to early retrys will block most DDNS provider
|
|
# providers answer is checked inside send_update() function
|
|
if [ $ERR_LAST -eq 0 ]; then
|
|
get_uptime LAST_TIME # we send update, so
|
|
echo $LAST_TIME > $UPDFILE # save LASTTIME to file
|
|
[ "$LOCAL_IP" != "$REGISTERED_IP" ] \
|
|
&& write_log 6 "Update successful - IP '$LOCAL_IP' send" \
|
|
|| write_log 6 "Forced update successful - IP: '$LOCAL_IP' send"
|
|
else
|
|
write_log 3 "IP update not accepted by DDNS Provider"
|
|
fi
|
|
fi
|
|
|
|
# now we wait for check interval before testing if update was recognized
|
|
# only sleep if VERBOSE_MODE <= 2 because otherwise nothing was send
|
|
[ $VERBOSE_MODE -le 2 ] && {
|
|
write_log 7 "Waiting $CHECK_SECONDS seconds (Check Interval)"
|
|
sleep $CHECK_SECONDS &
|
|
PID_SLEEP=$!
|
|
wait $PID_SLEEP # enable trap-handler
|
|
PID_SLEEP=0
|
|
} || write_log 7 "Verbose Mode: $VERBOSE_MODE - NO Check Interval waiting"
|
|
|
|
REGISTERED_IP="" # clear variable
|
|
get_registered_ip REGISTERED_IP # get registered/public IP
|
|
[ $use_ipv6 -eq 1 ] && expand_ipv6 "$REGISTERED_IP" REGISTERED_IP # on IPv6 we use expanded version
|
|
|
|
# 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 ))
|
|
[ $retry_count -gt 0 -a $ERR_UPDATE -gt $retry_count ] && \
|
|
write_log 14 "Updating IP at DDNS provider failed after $retry_count retries"
|
|
write_log 4 "Updating IP at DDNS provider failed - starting retry $ERR_UPDATE/$retry_count"
|
|
continue # loop to beginning
|
|
else
|
|
write_log 4 "Updating IP at DDNS provider failed"
|
|
write_log 7 "Verbose Mode: $VERBOSE_MODE - NO retry"; exit 1
|
|
fi
|
|
else
|
|
# we checked successful the last update
|
|
ERR_UPDATE=0 # reset error counter
|
|
fi
|
|
|
|
# force_update=0 or VERBOSE_MODE > 1 - leave here
|
|
[ $VERBOSE_MODE -gt 1 ] && write_log 7 "Verbose Mode: $VERBOSE_MODE - NO reloop"
|
|
[ $FORCE_SECONDS -eq 0 ] && write_log 6 "Configured to run once"
|
|
[ $VERBOSE_MODE -gt 1 -o $FORCE_SECONDS -eq 0 ] && exit 0
|
|
|
|
write_log 6 "Rerun IP check at $(eval $DATE_PROG)"
|
|
done
|
|
# we should never come here there must be a programming error
|
|
write_log 12 "Error in 'dynamic_dns_updater.sh - program coding error"
|