#!/bin/sh VERSION=1.1.5 # # nw-watchdog is a higly configurable network watchdog written # in POSIX shell script for use in Linux. # # Get the latest version from https://github.com/fraxflax/nw-watchdog # # nw-watchdog is free software written by Fredrik Ax . # Feel free to modify and/or (re)distribute it in any way you like. # (It's always nice to be mentioned though ;-) ) # # nw-watchdog comes with ABSOLUTELY NO WARRANTY. # # If you expirence any problems with nw-watchdog, are lacking # any functionality or just want to voice your opions about it, # feel free to contact me via e-mail: nw-watchdog@axnet.nu # # POSIX IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008): # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/contents.html # ########################################################################################### # DEFAULTS # Corresponding Command Line Options ########################################################################################### TARGET='' # TARGET (destination) HELP=''; HELPRE='(-h|--help)'; HELPV=y PAGERUSE=y; PAGERUSERE='(-M|--no-pager|--no-less|--no-more)'; PAGEUSEV='' PINGNEXTHOP=y; PINGNEXTHOPRE='(-N|-G|--no-ping-nexthop|--no-ping-gateway)'; PINGNEXTHOPV='' PINGTARGET=y; PINGTARGETRE='(-P|--no-ping-target)'; PINGTARGETV='' IPADDRALRT=y; IPADDRALRTRE='(-A|--no-ipaddr-alert)'; IPADDRALRTV='' IFCUPDOWN=y; IFCUPDOWNRE='(-R|--no-interface-reset)'; IFCUPDOWNV='' CONTINUOUSTOPOLOGYDETECT=y; CONTINUOUSTOPOLOGYDETECTRE='(-T|--no-continuous-topology-detect)'; CONTINUOUSTOPOLOGYDETECTV='' FORK=y; FORKRE='(-f|-D|--foreground|--no-daemonize)'; FORKV='' LISTSYSTEMD=''; LISTSYSTEMDRE='--list-systemd'; LISTSYSTEMDV=y VERBOSE='' VERBOSERE='(-v|--verbose)'; VERBOSEV=y DEBUG='' DEBUGRE='(-d|--debug)'; DEBUGV=y VERSHOW=''; VERSHOWRE='--version'; VERSHOWV=y V=4; VRE='(--verbosity-level|-V)'; VARE='[0-9]+' # integer >= 0 IFC=''; IFCRE='(--interface|-i)'; IFCARE='[^[:space:]/]+' # non-empty string without whitespaces and slashes STATICIFC=''; STATICIFCRE='(--force-interface|-I)'; STATICIFCARE=$IFCARE # same as IFCARE LOGFILE='/var/log/nw-watchdog.log'; LOGFILERE='(--logfile|-l)'; LOGFILEARE='.+' # non-emty string LOGSIZE=0; LOGSIZERE='(--logsize|-z)'; LOGSIZEARE='[0-9]+[kKmMgG]?' # integer >= 0, optional [kKmMgG] PIDFILE=/run/nw-watchdog.pid; PIDFILERE='(--pidfile|-p)'; PIDFILEARE='.+' # non-emty string SLOWUPTIMEOUT=3; SLOWUPTIMEOUTRE='(--slow-up-timeout|-t)'; SLOWUPTIMEOUTARE='[1-9][0-9]*' # integer >= 1 SLEEP=10; SLEEPRE='(--interval|--sleep|-s)'; SLEEPARE='[1-9][0-9]*' # integer >= 1 GRACE=20; GRACERE='(--ifup-grace|-g)'; GRACEARE='[1-9][0-9]*' # integer >= 1 MAXNOLINK=1; MAXNOLINKRE='(--max-nolink|-n)'; MAXNOLINKARE='[0-9]+' # integer >= 0 IFCUPT='ip link set up %{IFC}'; IFCUPTRE='(--ifcup|-u)'; IFCUPTARE='.+' # non-emty string IFCDOWNT='ip link set down %{IFC}'; IFCDOWNTRE='(--ifcdown|-U)'; IFCDOWNTARE='.+' # non-emty string ALERTCMDT='if which wall >/dev/null; then exec wall; else cat 1>&2; fi'; ALERTCMDTRE='(--alert|-a)'; ALERTCMDTARE='.+' # non-emty string SYSTEMDSERVICENAME=''; SYSTEMDSERVICENAMERE='--install-systemd'; SYSTEMDSERVICENAMEARE='.+' # non-emty string RMSYSTEMDSERVICE=''; RMSYSTEMDSERVICERE='--remove-systemd'; RMSYSTEMDSERVICEARE='.+' # non-emty string ############################################################################## SOPTIONS='HELP PAGERUSE PINGNEXTHOP PINGTARGET IPADDRALRT IFCUPDOWN CONTINUOUSTOPOLOGYDETECT FORK LISTSYSTEMD VERBOSE DEBUG VERSHOW' SOPTSRE='[hMNGPARTfDvd]' # Short options without arguments AOPTIONS='V IFC STATICIFC LOGFILE LOGSIZE PIDFILE SLOWUPTIMEOUT SLEEP GRACE MAXNOLINK IFCUPT IFCDOWNT ALERTCMDT SYSTEMDSERVICENAME RMSYSTEMDSERVICE' # Ensure we have a POSIX (enough) shell set >/dev/null 2>&1 && export PATH >/dev/null 2>&1 && umask 022 || { printf '\nThis script must be run in a POSIX compliant shell!\n\n' 1>&2 \ || echo 'This script must be run in a POSIX compliant shell!' 1>&2 exit 1 } # Let's put the "standard" paths first in PATH to ensure we get the standard utilities we need # and remove duplicates in PATH before exporting it NEWPATH='/sbin:/bin:/usr/sbin:/usr/bin' OIFS=$IFS IFS=: for p in $PATH; do case ":$NEWPATH:" in *:"$p":*) ;; *) NEWPATH="$NEWPATH:$p" ;; esac done IFS=$OIFS PATH=$NEWPATH export PATH DEPENDENCIES='cat cut date getent grep head id ip ping readlink sed sleep stat tail touch wc' lacking='' if printf '' 2>/dev/null; then deperr() { printf '\n(PATH=%s)\n\n~~~~~~~~~~~~~~~~\nDEPENDENCY ERROR\n~~~~~~~~~~~~~~~~\nThe following executable(s) are lacking in your PATH:\n %s\n... aborting!\n\n' "$PATH" "$*" 1>&2 exit 1 } else lacking='printf' deperr() { echo " (PATH=$PATH) ~~~~~~~~~~~~~~~~ DEPENDENCY ERROR ~~~~~~~~~~~~~~~~ The following executable(s) are lacking in your PATH: $* ... aborting!' " 1>&2 exit 1 } fi which sh >/dev/null 2>&1 || deperr which sh $lacking for x in $DEPENDENCIES; do which "$x" >/dev/null || lacking="$lacking $x"; done [ "$lacking" ] && deperr $lacking ERRMSG='' COLS=72; _b_=''; ___=''; __=''; _show_errmsg() { [ "$ERRMSG" ] || [ "$1" ] || return div=''; i=0; while [ $i -lt $COLS ]; do div="~$div"; i=$((i+1)); done printf '\n%s\n%s: ' "$_b_$div" "${_b_}ERROR$__" [ "$1" ] && { PTN=$1; shift printf "$PTN\n" "$@" } lines=$(printf %s "$ERRMSG" | sed -E 's/%/%%/g') printf "$lines" | while read l; do [ "$l" ] || break l=$(printf %s "$l" | sed -E 's/\t/\n /g') printf '%s %s\n' "$_b_*$__" "$l" done printf '%s\n\n' "$_b_$div" } nwwatchdog=${0##*/} _USAGE() { TPUTOK=1 which tput>/dev/null 2>&1 && { TPUTOK=0 COLS=$(tput cols) 2>/dev/null || TPUTOK=$((TPUTOK+1)) _b_=$(tput bold) 2>/dev/null || TPUTOK=$((TPUTOK+1)) ___=$(tput smul) 2>/dev/null || TPUTOK=$((TPUTOK+1)) #__=$(tput sgr0) 2>/dev/null || TPUTOK=$((TPUTOK+1)) 'tput sgr0' in combination with 'less -R' does not work on all terminal __=$(printf '\033[0m') # types, so always use the universal ANSI reset escape sequence instead } # ( '\033[Om' instead of '\033[m^O' ) [ $TPUTOK -eq 0 ] || { COLS=72; _b_=''; ___=''; __=''; } FMT=cat; which fmt >/dev/null && FMT="fmt -s -w $COLS" _show_errmsg "$@" $FMT<. Feel free to modify and/or (re)distribute it in any way you like. ... it's always nice to be mentioned though ;-) $_b_$nwwatchdog$__ comes with ABSOLUTELY NO WARRANTY. If you expirence any problems with ${_b_}nw-watchdog$__, are lacking any functionality or just want to voice your opions about it, feel free to contact me via e-mail. Get the latest version from https://github.com/fraxflax/nw-watchdog ${_b_}${___}TARGET$__ The mandatory (unless $_b_--list-systemd$__ or $_b_--remove-systemd$__ is specified) argument ${___}TARGET$__ is the target (destination) for which the connection is monitored. ${___}TARGET$__ can be an IP address or a resolvable hostname / FQDN. If it's a hostname / FQDN, it will be resolved to an IPv4 address (first one found by getent). The resolved IP address will be used for the monitoring. The name is continuously resolved and if the resolved ip address changes the new IP address will be used for the monitoring from there on. Use $_b_--no-continuous-topology-detect$__ to resolve the ${___}TARGET${__} only at startup and failed connectivity checks. ${___}TARGET$__ can be placed before, after or between valid OPTIONS. ${_b_}OPTIONS $__(no arguments) These options take no arguments, and may be specified in any order. They can be grouped (e.g. -vAP) in their short form, also having one of the OPTIONS that requires an argument last in the group. $_b_--help | -h $__ Shows this help, using \$PAGER if set to an executable, otherwise 'less' or 'more' if available in "/sbin:/bin:/usr/sbin:/usr/bin:\$PATH" All other options are ignored, apart from --no-pager which can be used to avoid using a pager. $_b_--no-pager | --no-less | --no-more | -M$__ Do NOT use a pager for help and error messages. $_b_--install-systemd$__ will enforce $_b_--no-pager$__ for the installed systemd service. $_b_--no-ping-target | -P $__ If the ${___}TARGET${__} is the next hop (on the same subnet), reachability of the ${___}TARGET${__} is checked by arp cache status and ping. If the ${___}TARGET${__} is not on the same subnet as the source, the reachability of the ${___}TARGET${__} is checked by pinging it in a certain pattern (see $_b_--slow-up-timeout$__ for details). $_b_--no-ping-target$__ disables the ping-checks for the ${___}TARGET${__}. Only connectivity to the NEXTHOP for the ${___}TARGET${__} is checked. This can be useful if ${___}TARGET${__} does not reply to ping, or if it desirable to only alert if there is no route to the ${___}TARGET${__} or NEXTHOP is unreachable. $_b_--no-ping-target$__ cannot be used in combination with $_b_--no-ping-nexthop$__. $_b_--no-ping-nexthop | -N | --no-ping-gateway | -G $__ By default, if the connectivity to the ${___}TARGET${__} cannot be verified, and the next hop (NEXTHOP) is not the ${___}TARGET${__} itself, the reachability of the NEXTHOP (usually a gateway) is checked, firstly by checking it's status in the arp cache and then by pinging it, rechecking the arp cache status upon failed ping. $_b_--no-ping-nexthop$__ disbles the reachaility check for the NEXTHOP so only connectivity to ${___}TARGET${__} itself is checked. Useful if the NEXTHOP is a peer-to-peer address and not setup to reply to ping. $_b_--no-ping-nexthop$__ cannot be used in combination with $_b_--no-ping-target$__. $_b_--no-ipaddr-alert | -A $__ Do not alert for not finding any global scope ip addresses on the source interface. $_b_--no-interface-reset | -R $__ Do not try to bring down and up interface after failed connectivity checks. (Do not try to "repair" the connection, just monitor it.) $_b_--no-continuous-topology-detect | -T $__ Normaly the topology (resolving the ip address of the ${___}TARGET${__}, detecting which source interface to use and the ip address of the NEXTHOP towards the ${___}TARGET${__}) is detected at startup and continuously monitored for changes. $_b_--no-continuous-topology-detect$__ disables the topology detection for as long as the ${___}TARGET${__} replies (or in combination with $_b_--no-ping-target$__, for as long as the NEXTHOP is reachable). The topology will only be detected at startup and if the ${___}TARGET${__} does not reply or if the NEXTHOP cannot be reached, meaning that routing changes making the ${___}TARGET${__} or NEXTHOP unreachable will not be detected as long as the ${___}TARGET${__} / NEXTHOP can be reached using the old topology. $_b_--force-interface$__ implies $_b_--no-continuous-topology-detect$__. $_b_--foreground | -f | --no-daemonize | -D $__ Run in foreground, do not fork / daemonize. $_b_--list-systemd$__ If systemd is installed, a brief status of all installed nw-watchdog systemd-services are listed to stdout. Cannot be combined with any other options. $_b_--verbose | -v $__ Shortcut for $_b_--verbosity-level=5$__ If used in combination with $_b_--verbosity-level$__, the specified $_b_--verbosity-level$__ level will take precedence. $_b_--debug | -d $__ Shortcut for: $_b_--verbosity-level=6 $_b_--logfile=- $_b_--logsize=0 $_b_--pidfile=/dev/null $_b_--slow-up-timeout=1 $_b_--sleep=3 $_b_--ifup-grace=5 $_b_--alert='cat' $_b_--foreground$__ If it's combined with any of the options it provides shortcuts for, the specified option will take precedence over the $_b_--debug$__ shortcut. This option cannot be combined with $_b_--install-systemd$__ (but it would be wise to test the configuration with $_b_--debug$__ before installing as a systemd service). $_b_--version$__ Prints the version of nw-watchdog to stdout and immediately exits. All other options are ignored. ${_b_}OPTIONS$__ (with ARGUMENT) These opions takes a single argument each and may be specified in any order. Specify with equalsign or space or no space between option and argument. They can only be grouped together with the shortform of the NO-ARGUMENT-OPTIONS above, and must be last in such groupings (e.g. -PAV5). EOU $FMT< 'vpnL' ; NEXTHOP='' -> '10.0.10.1' $_b_ ^^^^^^^^^^^^^^^^ $__ $_b_ This shows that vpnL is the interface that is up at start $__ 00:00:01 TRACE: Continuously checking if target is up... 00:00:01 TRACE: ... and for changes in topology 00:00:26 INFO: Detected topology: IFC='vpnL' -> 'vpnF' ; NEXTHOP='10.0.10.1' -> '10.0.10.1' $_b_ ^^^^^^^^^^^^^^^^ $__ $_b_ Here somebody replaced vpnL with the full tunnel vpnF $__ 00:00:31 INFO: Detected topology: IFC='vpnF' -> 'eth0' ; NEXTHOP='10.0.10.1' -> '192.168.0.1' $_b_ ^^^^^^^^^^^^^^^^ $__ $_b_ and here the full tunnel was brought down again (without replacing it with any of the other vpn-interfaces) $__ 00:00:32 TRACE: quick-up failed ... trying slow-up ... 00:00:32 TRACE: slow-up failed or ambigious result ... verifying ... 00:00:34 TRACE: No reply from target 'vpn.inside.dom' (10.0.10.1), checking link and topology. 00:00:35 TRACE: quick-up failed ... trying slow-up ... 00:00:35 TRACE: slow-up failed or ambigious result ... verifying ... $_b_ ^^^^^^^^^^^^^^^^ $__ $_b_ Here the $nwwatchdog gives up and concludes that the ${___}TARGET${__}$_b_ is down. $__ 00:00:37 ALERT: DOWN - Not getting replies from target 'vpn.inside.dom' (10.0.10.1) on interface 'eth0'. Resetting interface: ifdown vpnL ; ifdown vpnP ; ifdown vpnF ifup vpnL 00:00:37 INFO: Resetting interface (eth0). $_b_ ^^^^^^^^^^^^^^^^ ^^^^ $__ $_b_ This can be confusing since it's actually not eth0 that is reset (but it's the current source interface) $__ $_b_ Below we see that the watchdog actually brings down all the vpn-interfaces and brings up vpnL $__ 00:00:37 TRACE: sh -c 'ifdown vpnL ; ifdown vpnP ; ifdown vpnF' 00:00:38 TRACE: sh -c 'ifup vpnL' 00:00:38 TRACE: Sleeping for 35 seconds. $_b_ ^^^^^^^^^^^^^^^^ $__ $_b_ This is the grace period we have set in --ifup-grace $__ 00:01:13 INFO: Detected topology: IFC='eth0' -> 'vpnL' ; NEXTHOP='192.168.0.1' -> '10.0.10.1' 00:01:13 TRACE: Continuously checking if target is up... 00:01:13 TRACE: ... and for changes in topology 00:01:13 ALERT: UP - Target 'vpn.inside.dom' (10.0.10.1) is up. EOU $FMT </dev/null 2>&1; then LESS="$LESS -R"; export LESS # make sure less, if used, passes ANSI color escape sequences to terminal _USAGE "$@" | "$PAGER" elif which less >/dev/null 2>&1; then _USAGE "$@" | less -R elif which more >/dev/null 2>&1; then _USAGE "$@" | more else _USAGE "$@" fi [ "$ERRMSG" ] || [ "$1" ] || exit 0 exit 1 } SOPTRE='' for o in $SOPTIONS; do [ "$SOPTRE" ] && SOPTRE="$SOPTRE|" eval "r='\${${o}RE'}"; eval "SOPTRE=\"$SOPTRE$r\"" # should be \" or SOPTRE will contain variabel names instead of content done SOPTRE="($SOPTRE)" AOPTRE='' for o in $AOPTIONS; do [ "$AOPTRE" ] && AOPTRE="$AOPTRE|" eval "r='\${${o}RE'}"; eval "AOPTRE=\"$AOPTRE$r\"" # should be \" or AOPTRE will contain variabel names instead of content done AOPTRE="($AOPTRE)" NDOPTRE=$(printf '%s|%s' "$SOPTRE" "$AOPTRE" | sed -E 's/\(-*|\)//g' | sed -E 's/\|-*/\|/g') xOPTIONS=: while [ $# -gt 0 ]; do ARG=$1; shift if printf %s "$ARG" | grep -qE "^${SOPTRE}$"; then # No Argument Option for o in $SOPTIONS; do eval "R=\"\${${o}RE}\"" # should be \" or R will contain variabel names instead of content if printf %s "$ARG" | grep -qE "^$R$"; then eval "$o=\"\${${o}V}\"" # should be \" or o will contain variabel names instead of content xOPTIONS="${xOPTIONS}${o}:" break fi done elif printf %s "$ARG" | grep -qE "^-${SOPTSRE}(${NDOPTRE})"; then # Grouped Options A=$(printf %s "$ARG" | grep -oE '^..') B=$(printf %s "$ARG" | cut -c3-) set -- "$A" "-$B" "$@" elif printf %s "$ARG" | grep -qE "^${AOPTRE}"; then # Option with Argument if printf %s "$ARG" | grep -qE "^${AOPTRE}$"; then # Option [space] Argument OPT=$ARG ARG=$1 [ $# -gt 0 ] && shift elif printf %s "$ARG" | grep -qE "^${AOPTRE}=[^[:space:]]"; then # Option [equals] Argument OPT=${ARG%%=*} ARG=${ARG#*=} else # Option [no-space] Argument OPT=$(printf %s "$ARG" | grep -oE "^${AOPTRE}") ARG=$(printf %s "$ARG" | sed -E "s/^${AOPTRE}//") [ "$ARG" = '=' ] && { ERRMSG="'$OPT=' seem to lack an arugment.\tIf the argument really should be '=', use the syntax '$OPT=='.\n$ERRMSG" ARG='' } fi [ "$ARG" ] || ERRMSG="Option '$OPT' requires an argument.\n$ERRMSG" for o in $AOPTIONS; do eval "R=\"\${${o}RE}\"" # should be \" or AR will contain variabel names instead of content if printf %s "$OPT" | grep -qE "^$R$"; then if printf %s "$xOPTIONS" | grep -qE ":$o:"; then eval "oARG=\"\$${o}\"" [ "$oARG" = "$ARG" ] || { ERRMSG="Option '$OPT' $R specified multiple times with different values.\n$ERRMSG" break } else xOPTIONS="${xOPTIONS}${o}:" fi eval "AR=\"\${${o}ARE}\"" # should be \" or AR will contain variabel names instead of content printf %s "$ARG" | grep -qE "^$AR$" || { ERRMSG="Invalid argument '$ARG' for option '$OPT' $R.\n$ERRMSG" break } eval "$o='$ARG'" break fi done else # TARGET if printf %s "$ARG" | grep -qE '^-'; then ERRMSG="Invalid OPTION: '$ARG'\n$ERRMSG" else [ "$TARGET" ] && ERRMSG="Multiple TARGETs specified ('$TARGET' + '$ARG').\n$ERRMSG" TARGET=$ARG printf %s "$TARGET" \ | grep -qE '^((0*[0-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\.){3}(0*[0-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$' \ || printf %s "$TARGET" \ | grep -qE '^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$' || { ERRMSG="Invalid TARGET '$TARGET'. Must be an IP address or a valid hostname.\n$ERRMSG" } fi fi done [ "$VERSHOW" ] && { printf '%s\n' "$VERSION"; exit 0; } [ "$HELP" ] && ERRMSG='' USAGE [ "$LISTSYSTEMD" ] || printf %s "$xOPTIONS" | grep -qE ':RMSYSTEMDSERVICE:' && { printf %s "$xOPTIONS" | grep -qE ':.*:.*:' || [ "$TARGET" ] && \ ERRMSG='' USAGE %s "--list-systemd and --remove-systemd cannot be combined with any other option" [ -d /etc/systemd/system/. ] || USAGE "'/etc/systemd/system/' does not exists, is systemd installed and running?" [ -n "$LISTSYSTEMD" ] && { printf '\n\n--- Listing all loaded nw-watchdog systemd units: ---\n' systemctl list-units --all nw-watchdog-\* 2>/dev/null | grep -oE 'nw-watchdog-.*\.service.*' printf '\n--- Listing all installed nw-watchdog systemd units: ---\n' systemctl list-unit-files --all nw-watchdog-\* 2>/dev/null exit 0 } [ "$RMSYSTEMDSERVICE" ] || ERRMSG='' USAGE %s "--remove-systemd requires a service or unit name to remove" [ $(id -u) -eq 0 ] || USAGE %s "$nwwatchdog --remove-systemd must be run as root" systemctl daemon-reload || USAGE %s "Failed to reload systemd. Is systemd installed and running?" UNITNAME=$RMSYSTEMDSERVICE systemctl list-unit-files "$UNITNAME" >/dev/null 2>&1 || { UNITNAME="nw-watchdog-$RMSYSTEMDSERVICE.service" systemctl list-unit-files "$UNITNAME" >/dev/null 2>&1 \ || USAGE 'Neither "%s" or "%s" is a valid systemd service unit name.\n %s' \ "$RMSYSTEMDSERVICE" "$UNITNAME" \ 'Use --list-systemd to list all nw-watchdog systemd units.' } systemctl stop "$UNITNAME" || printf '\nWARNING: failed to stop "%s"\n' "$UNITNAME" 1>&2 systemctl disable "$UNITNAME" || printf '\nWARNING: failed to disable "%s"\n' "$UNITNAME" 1>&2 rm -vf "/etc/systemd/system/$UNITNAME" || printf '\nWARNING: failed to remove "/etc/systemd/system/%s"\n' "$UNITNAME" 1>&2 systemctl daemon-reload || printf '\nWARNING: Failed to reload systemd.\n' 1>&2 exit 0 } [ "$PINGTARGET" ] || [ "$PINGNEXTHOP" ] || \ ERRMSG="You must allow pinging of target and/or nexthop!\t(Can't combine --no-ping-target and --no-ping-nexthop!)\n$ERRMSG" [ "$INSTALLSYSTEMD" ] && [ "$DEBUG" ] && ERRMSG="--install-systemd cannot be combined with --debug\n$ERRMSG" [ "$TARGET" ] || ERRMSG="TARGET not specified! Specify IP address or valid hostname.\n$ERRMSG" ### Die showing the USAGE if found errors in options and arguments above ### [ "$ERRMSG" ] && USAGE %s 'Option Errors' [ "$DEBUG" ] && { printf %s "$xOPTIONS" | grep -qE ':V:' || V=6 printf %s "$xOPTIONS" | grep -qE ':LOGFILE:' || LOGFILE=- printf %s "$xOPTIONS" | grep -qE ':PIDFILE:' || PIDFILE=- printf %s "$xOPTIONS" | grep -qE ':SLOWUPTIMEOUT:' || SLOWUPTIMEOUT=1 printf %s "$xOPTIONS" | grep -qE ':SLEEP:' || SLEEP=3 printf %s "$xOPTIONS" | grep -qE ':GRACE:' || GRACE=5 printf %s "$xOPTIONS" | grep -qE ':ALERTCMDT:' || ALERTCMDT='cat' printf %s "$xOPTIONS" | grep -qE ':ALERTCMD:' || ALERTCMD='cat' FORK='' } [ "$LOGFILE" = '-' ] && { LOGSIZE=0 ; LOGSIZEb=0 ; LOGFILE='' ;} [ "$PIDFILE" = '-' ] && PIDFILE=/dev/null LOGSIZEb=$LOGSIZE case "$LOGSIZEb" in *[Kk]) LOGSIZEb=$(printf %s "$LOGSIZEb" | sed -E 's/[Kk]$//') ;; *[Mm]) LOGSIZEb=$(printf %s "$LOGSIZEb" | sed -E 's/[Mm]$//'); LOGSIZEb=$((LOGSIZEb*1024)) ;; *[Gg]) LOGSIZEb=$(printf %s "$LOGSIZEb" | sed -E 's/[Gg]$//'); LOGSIZEb=$((LOGSIZEb*1048576)) ;; esac LOGSIZEb=$((LOGSIZEb*1024)) dienl() { diePATN="$1 ... aborting!" ; shift _show_errmsg "$diePATN" "$@" exit 1 } [ "$LOGFILE" ] && { [ "$SYSTEMDSERVICENAME" ] || { LOGDIR=${LOGFILE%/*} if [ -d "$LOGDIR/." ] || mkdir -p "$LOGDIR"; then touch "$LOGFILE" 2>/dev/null || dienl '%s\n%s' "Cannot write to logfile '$LOGFILE'." \ 'Fix permissions, run as user with access or specify another --logfile.' else dienl 'Cannot create non-exiting directory for --logfile="%s"\n%s' "$LOGFILE" \ 'Fix permissions, run as user with access or specify another --logfile.' fi } } [ "$STATICIFC" ] && { CONTINUOUSTOPOLOGYDETECT='' ; IFC=$STATICIFC ;} log() { logPATN="%s $1"; shift logentry=$(printf "$logPATN" "$(date '+%Y-%m-%d %H:%M:%S %z')" "$@") if [ "$LOGFILE" ]; then [ "$LOGSIZEb" -gt 0 ] && { # Remove top 10 lines until log file is smaller than LOGSIZEb, # silently skip if we can't lock the file if which flock >/dev/null 2>&1; then ok=y while [ "$ok" ] && [ $(stat --printf=%s "$LOGFILE") -gt $LOGSIZEb ]; do flock -w1 "$LOGFILE" -c "cp '$LOGFILE' '$LOGFILE.$$' ; tail -n +11 '$LOGFILE.$$' >'$LOGFILE'; rm '$LOGFILE.$$'" || ok='' done else while [ $(stat --printf=%s $LOGFILE) -gt $LOGSIZEb ]; do cp "$LOGFILE" "$LOGFILE.$$" ; tail -n +11 "$LOGFILE.$$" > "$LOGFILE" ; rm "$LOGFILE.$$" done fi } if which flock >/dev/null 2>&1; then flock -w1 "$LOGFILE" -c "printf '%s\n' \"$logentry\" >>'$LOGFILE'" || { [ $V -ge 2 ] && { printf 'WARNING: Could not lock logfile temporarily logging to "%s"\n' "$LOGFILE.$$" 1>&2 } TLOGFILE=$LOGFILE LOGFILE="$LOGFILE.$$" printf '%s\n' "$logentry" >>"$LOGFILE" LOGFILE=$TLOGFILE } else printf '%s\n' "$logentry" >>"$LOGFILE" fi else printf '%s\n' "$logentry" fi } ALERTSTATE='INITIAL' alert() { [ $V -ge 3 ] || return STATE=$1; shift [ "$STATE" = ERROR ] || [ "$STATE" = WARNING ] || [ "$STATE" = INITIAL ] || { # always alert on ERROR, WARNING and INITIAL [ "$ALERTSTATE" = INITIAL ] && { # Do not alert if we are up after INITIAL [ "$STATE" = UP ] || [ "$STATE" = REACHABLE ] || [ "$STATE" = LINKUP ] && return } [ "$ALERTSTATE" = "$STATE" ] && return # Same state [ "$ALERTSTATE" = LINKDOWN ] && [ "$STATE" = DOWN ] && return # LINKDOWN implies DOWN [ "$ALERTSTATE" = LINKDOWN ] && [ "$STATE" = UNREACHABLE ] && return # LINKDOWN implies UNREACHABLE [ "$ALERTSTATE" = UNREACHABLE ] && [ "$STATE" = DOWN ] && return # UNREACHABLE implies DOWN [ "$ALERTSTATE" = UP ] && [ "$STATE" = REACHABLE ] && return # UP implies REACHABLE [ "$ALERTSTATE" = UP ] && [ "$STATE" = LINKUP ] && return # UP implies LINKUP [ "$REACHABLE" = REACHABLE ] && [ "$STATE" = LINKUP ] && return # REACHABLE implies LINKUP } ALERTCMD=$(printf %s "$ALERTCMDT" | sed -E "s/\%\{STATE\}/$STATE/g" | sed -E "s/\%\{LASTSTATE\}/$ALERTSTATE/g") if [ "$TARGET" ]; then ALERTCMD=$(printf %s "$ALERTCMD" | sed -E "s/\%\{TARGET\}/$TARGET/g") else ALERTCMD=$(printf %s "$ALERTCMD" | sed -E "s/\%\{TARGET\}/unspecified-target/g") fi if [ "$IFC" ]; then ALERTCMD=$(printf %s "$ALERTCMD" | sed -E "s/\%\{IFC\}/$IFC/g") else ALERTCMD=$(printf %s "$ALERTCMD" | sed -E "s/\%\{IFC\}/topology-not-yet-detected/g") fi if [ "$TADDR" ]; then ALERTCMD=$(printf %s "$ALERTCMD" | sed -E "s/\%\{TADDR\}/$TADDR/g") else ALERTCMD=$(printf %s "$ALERTCMD" | sed -E "s/\%\{TADDR\}/unresolved-target/g") fi if [ "$NEXTHOP" ]; then ALERTCMD=$(printf %s "$ALERTCMD" | sed -E "s/\%\{NEXTHOP\}/$NEXTHOP/g") else ALERTCMD=$(printf %s "$ALERTCMD" | sed -E "s/\%\{NEXTHOP\}/topology-not-yet-detected/g") fi alertPATN=$1 ; shift printf "\n\n$nwwatchdog $STATE ALERT!!!\n$alertPATN\n\n\n" "$@" | sh -c "$ALERTCMD" case "$STATE" in "ERROR") LSTATE=' ERROR:' ;; "WARNING") LSTATE='WARNING:' ;; *) ALERTSTATE=$STATE LSTATE=" ALERT: $STATE -" ;; esac log "$LSTATE $alertPATN" "$@" } error() { [ $V -ge 1 ] || return alert ERROR "$@" } die() { diePATN="$1 ... aborting!" ; shift error "$diePATN" "$@" _show_errmsg "$diePATN" "$@" exit 1 } warn() { [ $V -ge 2 ] || return alert WARNING "$@" } info() { [ $V -ge 4 ] || return infoPATN=$1 ; shift log " INFO: $infoPATN" "$@" } trace() { [ $V -ge 5 ] || return tracePATN=$1 ; shift log " TRACE: $tracePATN" "$@" } debug() { [ $V -ge 6 ] || return debugPATN=$1 ; shift log " DEBUG: $debugPATN" "$@" } ifreset() { [ "$IFCUPDOWN" ] || { info 'Not resetting interface due to --no-interface-reset.' return 0 } [ "$IFC" ] || die 'Internal ERROR: No interface to reset!' info 'Resetting interface (%s).' "$IFC" trace 'sh -c "%s"' "$IFCDOWN" IFCDD=$(sh -c "$IFCDOWN" 2>&1) EXITCODE=$? debug 'ifcdown: "%s"\n\texit code %d and output:\n\t%s' "$IFCDOWN" $EXITCODE "$IFCDD" sleep 1 trace 'sh -c "%s"' "$IFCUP" EXITCODE=0 IFCUD=$(sh -c "$IFCUP" 2>&1) || { EXITCODE=$? debug 'ifcup: "%s"\n\texit code %d and output:\n\t%s' "$IFCUP" $EXITCODE "$IFCUD" warn '%s\n\t%s' \ "Could not reset interface '$IFC'." \ "Check your --if-up and --if-down commands and/or privilegdes of effective user (userid=$(id -u))." [ "$1" ] && return 1 } debug '"%s" exited with %d and output: "%s"' "$IFCUP" $EXITCODE "$IFCUD" trace 'Sleeping for %d seconds.' $GRACE sleep $GRACE return $EXITCODE } linkcheck() { debug 'Checking "%s" link state' "$IFC" [ "$IFC" ] || die 'Internal ERROR: No interface to check link for!' NOLINK=0 while :; do ip link show $IFC>/dev/null 2>&1 && { ip link show $IFC | grep -oq 'state DOWN' || { IPADDRS=''; ip addr show dev $IFC | grep -qE 'inet6? .* scope global' && IPADDRS=y [ "$IPADDRS" ] && [ "$IPADDRALRT" ] \ || warn '%s\n\t%s' \ "There is no IPv4 or IPv6 global scope ip address on interface '$IFC'!" \ "If that is ok, you can disable these alerts with --no-ipaddr-alert." alert LINKUP 'Link up on interface "%s"!' "$IFC" return 0 } } if [ "$IFCUPDOWN" ]; then NOLINK=$((NOLINK+1)) [ $MAXNOLINK -gt 0 -a $NOLINK -gt $MAXNOLINK ] && { ip link show $IFC>/dev/null 2>&1 || { alert LINKDOWN 'Interface "%s" ... device does not exist!' "$IFC" return 2 } alert LINKDOWN 'Link down on interface "%s" after %d reset attempts (with %d seconds in between)!' "$IFC" $MAXNOLINK $GRACE return 1 } info 'No link on "%s"... reset attempt %d out of %d (0=infinite)' "$IFC" $NOLINK $MAXNOLINK ifreset "$1" else alert LINKDOWN 'Link down on interface "%s"!' "$IFC" return 1 fi done } nexthop() { NIFC=$(ip route get $TADDR | grep -o 'dev [^ ]*' | while read -r _ h _; do echo $h; done) if [ "$NIFC" ]; then NNEXTHOP=$(ip route get $TADDR | grep -oE 'via [^ ]*' | while read -r _ h _; do echo $h; done) [ "$NNEXTHOP" ] || { NNEXTHOP=$TADDR debug 'No nexthop detected (ok if same subnet (incl. host-local addr) or peer-to-peer addr), using target as nexthop... ' # If it's a host-local address 'ip route get' will say lo is source interface even if addr is on different interface # which will cause 'ping -I$IFC' to fail, so let's figure which interface holds the address is instead [ "$NIFC" = lo ] && { nexthopADDR=$(printf %s "$TADDR" | sed -E 's/\./\\./g') ip addr | grep -qE "inet $nexthopADDR( |\/)" && { LIFC=$(ip addr | grep -B9999 -E "inet $nexthopADDR( |\/)" | grep -oE '^[0-9]+:\s+[^@:]+' | tail -1 | sed -E 's/.*\s//') [ "$LIFC" ] && { debug 'Found %s on local interface "%s" (overriding "%s")' "$TADDR" "$LIFC" "$NIFC" NIFC=$LIFC } } } } if [ "$IFC" = "$NIFC" ] && [ "$NEXTHOP" = "$NNEXTHOP" ]; then debug 'No topology changes detected. IFC="%s" NEXTHOP="%s"' "$IFC" "$NEXTHOP" return 0 elif [ -z "$STATICIFC" ]; then info 'Detected topology: IFC="%s" -> "%s" ; NEXTHOP="%s" -> "%s"' "$IFC" "$NIFC" "$NEXTHOP" "$NNEXTHOP" IFC=$NIFC NEXTHOP=$NNEXTHOP IFCUP=$(printf %s "$IFCUPT" | sed -E "s/\%\{IFC\}/$IFC/g") IFCDOWN=$(printf %s "$IFCDOWNT" | sed -E "s/\%\{IFC\}/$IFC/g") return 0 elif [ "$STATICIFC" = "$NIFC" ]; then info 'Detected topology on forced interface "%s": NEXTHOP="%s" -> "%s"' "$IFC" "$NEXTHOP" "$NNEXTHOP" debug 'STATICIFC="%s" IFC="%s" NIFC="%s" NEXTHOP="%s" NNEXTHOP="%s"' \ "$STATICIFC" "$IFC" "$NIFC" "$NEXTHOP" "$NNEXTHOP" NEXTHOP=$NNEXTHOP return 0 else info 'Ignoring conflicting topology: Route to target "%s" (%s) is on interface "%s", but --force-interface="%s"' \ "$TARGET" "$TADDR" "$NIFC" "$STATICIFC" return 1 fi else info 'No route to target "%s" (%s) available.' "$TARGET" "$TADDR" debug 'Hanging on to interface "%s".' "$IFC" return 1 fi } [ "$FORK" ] || [ "$SYSTEMDSERVICENAME" ] && { debug 'MAINPID=%d BASENAME=%s COMM=%s' $$ "$nwwatchdog" "$(cat /proc/$$/comm)" NOPTS='M' # don't use a pager for output from background / systemd processes [ "$PINGNEXTHOP" ] || NOPTS="${NOPTS}N" [ "$PINGTARGET" ] || NOPTS="${NOPTS}P" [ "$IPADDRALRT" ] || NOPTS="${NOPTS}A" [ "$IFCUPDOWN" ] || NOPTS="${NOPTS}R" [ "$CONTINUOUSTOPOLOGYDETECT" ] || NOPTS="${NOPTS}T" AOPTS="V$V -t$SLOWUPTIMEOUT -s$SLEEP -g$GRACE -n$MAXNOLINK -u'$IFCUPT' -U'$IFCDOWNT' -a'$ALERTCMDT' -z$LOGSIZE" if [ "$STATICIFC" ]; then AOPTS="$AOPTS -I$IFC" elif [ "$IFC" ]; then AOPTS="$AOPTS -i$IFC"; fi debug 'NOPTS=%s\nAOPTS=%s\n' "$NOPTS" "$AOPTS" } ### Systemd service creation [ "$SYSTEMDSERVICENAME" ] && { LOGFILE='' printf %s "$SYSTEMDSERVICENAME" | grep -qEe '^[a-zA-Z0-9_-]+$' || \ USAGE 'Invalid systemd service name "%s"\n %s\n %s' "$SYSTEMDSERVICENAME" \ 'The SYSTEMDSERVICENAME must contain only alphanumeric characters, hyphens and underscores' \ 'and be no longer than 236 characters.' [ $(printf %s "$SYSTEMDSERVICENAME" | wc -c) -gt 236 ] && \ USAGE 'Systemd service name\n "%s"\n %s' \ "$SYSTEMDSERVICENAME" "is too long (max 236 characters)" [ $(id -u) -eq 0 ] || USAGE %s "$nwwatchdog --install-systemd must be run as root" [ -d /etc/systemd/system/. ] || USAGE "'/etc/systemd/system/' does not exists,\n systemd '%s.service' NOT installed!" \ "$SYSTEMDSERVICENAME" cat>/etc/systemd/system/nw-watchdog-$SYSTEMDSERVICENAME.service<&1 | grep -q 'REACHABLE' && { debug '%s is arp1-up on %s' "$isupADDR" "$IFC"; return 0 ;} ping -q -w2 -c1 -I$IFC $isupADDR >/dev/null 2>&1 && { debug '%s is arp-ping-up on %s' "$isupADDR" "$IFC"; return 0 ;} # also refreshes arp-cache ip neigh get dev $IFC $isupADDR 2>&1 | grep -q 'REACHABLE' && { debug '%s is arp2-up on %s' "$isupADDR" "$IFC"; return 0 ;} } # quick check if line is already up ping -q -w1 -c1 -I$IFC $isupADDR >/dev/null 2>&1 && { debug '%s is quick-up on %s' "$isupADDR" "$IFC" return 0 } trace 'quick-up failed ... trying slow-up ...' ping -qA -w$SUDEADLINE -W$SLOWUPTIMEOUT -c$SUCOUNT -I$IFC $isupADDR >/dev/null 2>&1 && { trace '%s is slow-up via %s' "$isupADDR" "$IFC" return 0 } # If ping does not receive any reply packets at all it will exit # with code 1. If a packet count and deadline are both specified, # and fewer than count packets are received by the time the # deadline has arrived, it will also exit with code 1. On other # error it exits with code 2. Otherwise it exits with code 0. This # makes it possible to use the exit code to see if a host is alive # or not. # double check that we're up (the above could potentially # (but unlikely due to -A) return 1 or 2 even if we're up) trace 'slow-up failed or ambigious result ... verifying ...' ping -q -w2 -c1 -I$IFC $isupADDR >/dev/null 2>&1 && { trace '$isupADDR is verified-up via $IFC' return 0 } # We don't get traffic through debug '%s is not-up on %s' "$isupADDR" "$IFC" [ "$2" = noresolve ] && return 1 isupOADDR=$TADDR if resolve_target; then [ "$isupOADDR" = "$TADDR" ] || { info 'Target "%s" now resolves to "%s", instead of "%s".' "$TARGET" "$TADDR" "$isupOADDR" nexthop && { if [ "$isupADDR" = "$isupOADDR" ]; then # it was target that failed, test again with new TADDR isupADDR=$TADDR else # it was nexthop that failed, test again with new NEXTHOP isupADDR=$NEXTHOP fi isup "$isupADDR" noresolve && return 0 } } else TADDR=$isupOADDR warn 'Target "%s" no longer resolves to an ip address, will keep using "%s".' "$TARGET" "$TADDR" fi return 1 } [ "$PIDFILE" ] || PIDFILE=/run/nw-watchdog.pid PIDDIR=${PIDFILE%/*} if [ -d "$PIDDIR/." ] || mkdir -p "$PIDDIR"; then touch "$PIDFILE" 2>/dev/null || die '%s\n%s' "Cannot write to pidfile '$PIDFILE'." \ 'Fix permissions, run as user with access or specify another --pidfile.' else die 'Cannot create directory for --pidfile="%s"\n%s' "$PIDFILE" \ 'Fix permissions, run as user with access or specify another --pidfile.' fi PID=$(cat "$PIDFILE") [ "$PID" -gt 0 ] 2>/dev/null && [ -d /proc/$PID ] && die "Process already running with pid $PID" ### Forking [ "$FORK" ] && { if [ "$LOGFILE" ]; then AOPTS="$AOPTS -l'$LOGFILE'" else AOPTS="$AOPTS -l-"; fi AOPTS="$AOPTS -p'$PIDFILE'" if [ -x "$0" ]; then trace 'forking: %s' "'$0' '$TARGET' -D$NOPTS$AOPTS" { sleep 0.25 ; eval "exec '$0' '$TARGET' -D$NOPTS$AOPTS" ;} & else trace 'forking: %s' "sh '$0' '$TARGET' -D$NOPTS$AOPTS" { sleep 0.25 ;eval "exec sh '$0' '$TARGET' -D$NOPTS$AOPTS" ;} & fi exit 0 } printf $$ > "$PIDFILE" debug 'PID=%d BASENAME=%s COMM=%s' $$ "$nwwatchdog" "$(cat /proc/$$/comm)" debug '\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s' \ '~~~ SETTINGS ~~~' \ "TARGET='$TARGET'" \ "PINGNEXTHOP='$PINGNEXTHOP'" \ "PINGTARGET='$PINGTARGET'" \ "IPADDRALRT='$IPADDRALRT'" \ "CONTINUOUSTOPOLOGYDETECT='$CONTINUOUSTOPOLOGYDETECT'" \ "IFCUPDOWN='$IFCUPDOWN'" \ "FORK='$FORK'" \ "V='$V'" \ "IFC='$IFC'" \ "STATICIFC='$STATICIFC'" \ "LOGFILE='$LOGFILE'" \ "LOGSIZE='$LOGSIZE' ($LOGSIZEb bytes)" \ "PIDFILE='$PIDFILE'" \ "SLOWUPTIMEOUT='$SLOWUPTIMEOUT'" \ "SLEEP='$SLEEP'" \ "GRACE='$GRACE'" \ "MAXNOLINK='$MAXNOLINK'" \ "IFCUPT='$IFCUPT'" \ "IFCDOWNT='$IFCDOWNT'" \ "ALERTCMDT='$ALERTCMDT'" TADDR='' printf %s "$TARGET" | grep -qE '^((0*[0-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\.){3}(0*[0-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$' && { TADDR=$(printf %s "$TARGET" | sed -E 's/(^|\.)0+([1-9]+)/\1\2/g') # Clean up potential oktet zero-padding TARGET=$TADDR debug 'TARGET is an IP address: "%s"' "$TARGET" } [ "$IFC" ] && { IFCUP=$(printf %s "$IFCUPT" | sed -E "s/\%\{IFC\}/$IFC/g") IFCDOWN=$(printf %s "$IFCDOWNT" | sed -E "s/\%\{IFC\}/$IFC/g") ip link show $IFC>/dev/null 2>&1 || { # device does not exist if [ "$IFCUPDOWN" ]; then info 'Interface "%s" is not available (no such device), trying to bring it up.' "$IFC" sh -c "$IFCUP" >/dev/null 2>&1 trace 'Sleeping for %d seconds.' $GRACE sleep $GRACE if ip link show $IFC>/dev/null 2>&1; then [ "$STATICIFC" ] || { ip link show $IFC | grep -oq 'state DOWN' && \ alert INITIAL 'Initial interface "%s" brought up, but has no link, will continue detecting topology ...' "$IFC" } else if [ "$STATICIFC" ]; then warn '\n\t--force-interface "%s" is not available (no such device),\n\t%s\n\t%s\n\t%s\n\t%s' "$IFC" \ 'Will keep trying to bring it up!' \ '... or you might want to check the nw-watchdog options and ' \ 'restart this instance with correct ones if wrong.' else alert INITIAL \ 'Initial interface "%s" could not be brought up (no such device),\n\t%s' "$IFC" \ 'will continue detecting topology without it ...' fi fi elif [ "$STATICIFC" ]; then warn '\n\t--force-interface "%s" is not available (no such device),\n\t%s\n\t%s\n\t%s\n\t%s' "$IFC" \ 'and ---no-interface-reset prevents bringing it up!' \ "The status will be checked every $GRACE seconds until it comes up." \ 'Bring the interface up, or check the nw-watchdog options and ' \ 'restart this instance with correct ones if wrong.' else alert INITIAL 'Initial interface "%s" is not available (no such device),\n\t%s\n\t%s' "$IFC" \ 'and ---no-interface-reset prevents bringing it up!' \ 'Detecting topology without initial interface.' fi } if [ "$STATICIFC" ]; then while ! linkcheck; do sleep $GRACE; done elif ip link show $IFC | grep -oq 'state DOWN'; then alert INITIAL 'Initial interface "%s" has no link, will continue detecting topology ...' "$IFC" fi } while [ -z "$TADDR" ]; do resolve_target || { warn 'Target "%s" does not resolve to an IPv4 address. Make sure name resoloution funtions on the system\n\t%s\n\t%s' \ "$TARGET" \ '... or stop this instance and start it again using a valid hostname / FQDN or IP address' \ "... sleeping for $GRACE seconds." sleep $GRACE } done if [ "$IFC" ]; then nexthop || { linkcheck && { nexthop || { ifreset && nexthop ;};};} else while ! nexthop; do warn 'No interface specified and no route to target "%s" (%s) available, make sure interface comes up,\n\t%s\n\t%s' \ "$TARGET" "$TADDR" \ 'or stop this instance and start it again using --interface or --force-interface to bring it up automatically.' \ "... sleeping for $GRACE seconds." sleep $GRACE done fi while :; do if [ "$PINGTARGET" ]; then trace 'Continuously checking if target is up...' [ "$CONTINUOUSTOPOLOGYDETECT" ] && trace '... and for changes in topology' while isup $TADDR; do alert UP 'Target "%s" (%s) is up.' "$TARGET" "$TADDR" debug 'Target "%s" (%s) is up. Sleeping for %d seconds.' "$TARGET" "$TADDR" $SLEEP sleep $SLEEP [ "$CONTINUOUSTOPOLOGYDETECT" ] && { resolve_target; nexthop ;} done trace 'No reply from target "%s" (%s), checking link and topology.' "$TARGET" "$TADDR" if nexthop || { linkcheck && nexthop ;}; then isup "$TADDR" || { if [ "$PINGNEXTHOP" ]; then if isup "$NEXTHOP" noresolve; then alert DOWN \ 'Not getting replies from target "%s" (%s) on interface "%s", but nexthop "%s" is reachable... sleeping for %d seconds.' \ "$TARGET" "$TADDR" "$IFC" "$NEXTHOP" $GRACE sleep $GRACE else alert UNREACHABLE \ 'Not getting replies from target "%s" (%s) on interface "%s", and nexthop "%s" is not reachable.\n\t%s\n\t%s\n\t%s' \ "$TARGET" "$TADDR" "$IFC" "$NEXTHOP" 'Resetting interface:' "$IFCDOWN" "$IFCUP" ifreset && nexthop fi else alert DOWN \ 'Not getting replies from target "%s" (%s) on interface "%s".\n\t%s\n\t%s\n\t%s' \ "$TARGET" "$TADDR" "$IFC" 'Resetting interface:' "$IFCDOWN" "$IFCUP" ifreset && nexthop fi } else alert UNREACHABLE \ 'Not getting replies from target "%s" (%s) and no route to target available, resetting interface (%s):\n\t%s\n\t%s' \ "$TARGET" "$TADDR" "$IFC" "$IFCDOWN" "$IFCUP" ifreset && nexthop fi else trace 'Continuously checking if nexthop is reachable...' [ "$CONTINUOUSTOPOLOGYDETECT" ] && trace '... and for changes in topology' while isup $NEXTHOP; do alert REACHABLE 'Nexthop "%s" for target "%s" (%s) is reachable.' "$NEXTHOP" "$TARGET" "$TADDR" debug 'Nexthop "%s" for target "%s" (%s) is reachable. Sleeping for %d seconds.' "$NEXTHOP" "$TARGET" "$TADDR" $SLEEP sleep $SLEEP [ "$CONTINUOUSTOPOLOGYDETECT" ] && { resolve_target; nexthop ;} done trace 'nexthop "%s" (for --no-ping-target "%s" (%s)) on interface "%s", not reachable, checking link and nexthop.' \ "$NEXTHOP" "$TARGET" "$TADDR" "$IFC" if { resolve_target && nexthop ;} || { linkcheck && resolve_target && nexthop ;}; then isup "$NEXTHOP" noresolve || { alert UNREACHABLE \ 'nexthop "%s" (for --no-ping-target "%s" (%s)), not reachable, resetting interface ("%s"):\n\t%s\n\tsleep 1\n\t%s' \ "$NEXTHOP" "$TARGET" "$TADDR" "$IFC" "$IFCDOWN" "$IFCUP" ifreset && nexthop } else alert UNREACHBLE \ 'No route to --no-ping-target "%s" (%s). Resetting interface (%s):\n\t%s\n\tsleep 1\n\t%s' \ "$TARGET" "$TADDR" "$IFC" "$IFCDOWN" "$IFCUP" ifreset && nexthop fi fi done die 'Internal error: EOF'