#!/bin/bash
# shellcheck disable=SC1091,SC1090
# vim: set noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 :

[ -n "$1" ] || exit

. /lib/functions.sh
. /usr/lib/unbound/iptools.sh
. /lib/functions/network.sh

# retrieve args
OMR_TRACKER_INTERFACE="$1"
shift

# export vars
export OMR_TRACKER_INTERFACE
export OMR_TRACKER_HOST
export OMR_TRACKER_HOST6
export OMR_TRACKER_LATENCY_IP
export OMR_TRACKER_LATENCY_IP6
export OMR_TRACKER_TIMEOUT
export OMR_TRACKER_SIZE
export OMR_TRACKER_MAX_TTL
export OMR_TRACKER_LOSS
export OMR_TRACKER_JITTER
export OMR_TRACKER_FAILURE_CONGESTION
export OMR_TRACKER_RECOVERY_CONGESTION
export OMR_TRACKER_CONGESTION_SCORE
export OMR_TRACKER_RTT_MIN
export OMR_TRACKER_RTT_MAX
export OMR_TRACKER_STATUS
export OMR_TRACKER_STATUS_MSG
export OMR_TRACKER_PREV_STATUS
export OMR_TRACKER_DEVICE
export OMR_TRACKER_PREV_DEVICE
export OMR_TRACKER_DEVICE_IP
export OMR_TRACKER_DEVICE_IP6
export OMR_TRACKER_PREV_DEVICE_IP
export OMR_TRACKER_PREV_DEVICE_IP6
export OMR_TRACKER_DEVICE_GATEWAY
export OMR_TRACKER_DEVICE_GATEWAY6
export OMR_TRACKER_IPV6
export OMR_TRACKER_PROTO
export OMR_TRACKER_INTERVAL_TRIES
export OMR_TRACKER_INTERVAL
export OMR_TRACKER_NO_BIND
export OMR_TRACKER_TYPE
export OMR_TRACKER_GATEWAY_IS_REMOTE
export OMR_TRACKER_RECOVERING

#dscp=56   # set DSCP CS7 (56) in outgoing packets

# The probes of a 3g/qmi/ncm interface go out through a host route added for
# the probe. Its own protocol and metric make it the only route the deletes
# can match: the kernel ignores "src" when it deletes an IPv6 route, and an
# IPv4 delete matched any route with the same prefsrc, so they used to remove
# a user's route or route6 to the probed host via the same gateway and device.
# The metric also keeps the add clear of such a route; two trackers probing
# the same host still collide on it, which is what the add loop waits for.
OMR_TRACKER_PROBE_ROUTE="proto 199 metric 4242"

# On TERM from procd (stop or restart), kill the probe in progress (see
# _probe) and clean up. A post-tracking hook in progress is let finish: bash
# runs the trap once the command it runs in the foreground is done, and the
# init script gives the tracker the time for it (term_timeout), so a hook
# never keeps running next to the tracker started after this one.
_omr_exit() {
	jobs -p | xargs -r kill 2>/dev/null
	rm -f "$_probe_out"
	# A probe cut short by the signal never deleted its host route
	if [ -n "$_probe_route" ] && [ -n "$OMR_TRACKER_DEVICE" ]; then
		ip route flush dev "$OMR_TRACKER_DEVICE" $OMR_TRACKER_PROBE_ROUTE > /dev/null 2>&1
		ip -6 route flush dev "$OMR_TRACKER_DEVICE" $OMR_TRACKER_PROBE_ROUTE > /dev/null 2>&1
	fi
	exit 0
}
trap '_omr_exit' INT TERM EXIT

#initial_hosts="$OMR_TRACKER_HOSTS"
#initial_hosts6="$OMR_TRACKER_HOSTS6"
initial_timeout="$OMR_TRACKER_TIMEOUT"
wait_test=${OMR_TRACKER_WAIT_TEST:-0}

# set constants for rto updating
# we've changed the default values of the RFC to K=3 and beta=0.25 instead of K=4 and beta=0.125
# this allows us to lower the gap between rtt and rto when the latency increases quickly as our
# measurements are less frequent than the standard timeout computation of the TCP stack
_init_rto() {
	srtt=
	rttvar=
	rto_init=$(( initial_timeout * 1000 ))
	rto=$rto_init
}

# update rto as explained in rfc6298
# rto    : retransmission timeout.
# rtt    : round trip time
# rttvar : round trip time variance
# srtt   : smoothed round trip time
# alpha  : smoothing coeff for srtt
# beta   : smoothing coeff for rttvar
# K      : coeff to increase rttvar impact on rto computation
_update_rto() {
	if [ -z "$srtt" ]; then
		srtt=$1
		rttvar=$(( $1 / 2 ))
		#"
	else
		diff=$((srtt - $1))
		rttvar=$(( (75 * rttvar + 25 * (diff >= 0 ? diff : -diff)) / 100 ))
		srtt=$(( (75 * srtt + 25 * $1) / 100 ))
	fi
	rto=$((tmp = srtt + 3 * rttvar, tmp > rto_init ? tmp : rto_init))
}

_restart() {
	OMR_TRACKER_HOST="${OMR_TRACKER_HOSTS%% *}"
	OMR_TRACKER_HOST6="${OMR_TRACKER_HOSTS6%% *}"
	[ "$OMR_TRACKER_HOST" = "$OMR_TRACKER_HOSTS" ] || {
		OMR_TRACKER_HOSTS="${OMR_TRACKER_HOSTS#* } $OMR_TRACKER_HOST"
		_init_rto
	}
	[ "$OMR_TRACKER_HOST6" = "$OMR_TRACKER_HOSTS6" ] || {
		OMR_TRACKER_HOSTS6="${OMR_TRACKER_HOSTS6#* } $OMR_TRACKER_HOST6"
		_init_rto
	}
}

# Replace OMR_TRACKER_HOST / OMR_TRACKER_HOST6 by its address, resolved again
# when the host changes or every 5 minutes
_resolve_host4() {
	printf -v _resolve_now '%(%s)T' -1
	if [ "$OMR_TRACKER_HOST" != "$_host4_raw" ] || [ -z "$_host4_resolved" ] || [ $((_resolve_now - _host4_resolve_time)) -gt 300 ]; then
		_host4_raw="$OMR_TRACKER_HOST"
		_host4_resolved=$(resolveip -4 -t 5 "$OMR_TRACKER_HOST" | head -n 1 | tr -d "\n")
		_host4_resolve_time=$_resolve_now
	fi
	[ -n "$_host4_resolved" ] && OMR_TRACKER_HOST="$_host4_resolved"
}

_resolve_host6() {
	printf -v _resolve_now '%(%s)T' -1
	if [ "$OMR_TRACKER_HOST6" != "$_host6_raw" ] || [ -z "$_host6_resolved" ] || [ $((_resolve_now - _host6_resolve_time)) -gt 300 ]; then
		_host6_raw="$OMR_TRACKER_HOST6"
		_host6_resolved=$(resolveip -6 -t 5 "$OMR_TRACKER_HOST6" | head -n 1 | tr -d "\n")
		_host6_resolve_time=$_resolve_now
	fi
	[ -n "$_host6_resolved" ] && OMR_TRACKER_HOST6="$_host6_resolved"
}

# A host that did not resolve is left as a name, which "ip route add" never
# takes: probing it without a route beats retrying the add forever
_is_ip() {
	case "$1" in
		*:*) return 0 ;;
		""|*[!0-9.]*) return 1 ;;
	esac
}

# Read the congestion score (0-100) omr-metrics computed for this interface
# on the *previous* post-tracking cycle (post-tracking.d/040-metrics runs
# after this loop's quality gate, so the freshest score is always one cycle
# behind - same lag as any other cross-hook data). Silently yields an empty
# OMR_TRACKER_CONGESTION_SCORE when omr-metrics isn't installed or hasn't
# written a sample yet, so the check below is a no-op in that case.
_read_congestion() {
	OMR_TRACKER_CONGESTION_SCORE=
	[ -z "$OMR_TRACKER_FAILURE_CONGESTION" ] && [ -z "$OMR_TRACKER_RECOVERY_CONGESTION" ] && return
	command -v jsonfilter >/dev/null 2>&1 || return
	local metrics_file="${OMR_METRICS_DIR:-/tmp/metrics}/${OMR_TRACKER_INTERFACE}.json"
	[ -f "$metrics_file" ] || return
	OMR_TRACKER_CONGESTION_SCORE=$(jsonfilter -i "$metrics_file" -q -e '@.congestion.score' 2>/dev/null)
}

# Kernel link state of a device (UP / DOWN / UNKNOWN / LOWERLAYERDOWN, empty
# when the device is gone), assigned to the variable named by $1 so the
# caller pays no subshell for it.
# "ip -br link" prints "<dev> <STATE> <mac> <flags>" in space-padded
# columns, which is why the state is read as a field: stripping the device
# name with "${x#* }" leaves the padding behind and a following "${x%% *}"
# then yields an empty string. One process instead of the
# ip|jsonfilter|tr pipeline this used to run every cycle for every
# interface.
_device_state_var() {
	local _var="$1" _dev="$2" _ignore _state=""
	if [ -n "$_dev" ]; then
		read -r _ignore _state _ignore <<<"$(ip -br link show dev "$_dev" 2>/dev/null)"
	fi
	eval "$_var=\$_state"
}

# Highest global IPv6 address of a device, prefix length stripped, assigned
# to the variable named by $1. One awk instead of the
# ip|sort|grep|awk|cut pipeline this used to run every cycle for every
# interface, including the ones that have no IPv6 at all.
_device_ip6_var() {
	local _var="$1" _dev="$2" _ip6=""
	if [ -n "$_dev" ]; then
		_ip6=$(ip -6 addr show dev "$_dev" scope global 2>/dev/null | awk '
			$1 == "inet6" { split($2, a, "/"); if (a[1] > best) best = a[1] }
			END { if (best != "") print best }')
	fi
	eval "$_var=\$_ip6"
}

# netifd keeps a static (force_link) interface up across a carrier loss and
# sends no hotplug event when the link returns, while the kernel has flushed
# the device's routes from the interface table on link-down. Nothing put them
# back until 001-initialize noticed, after a full test phase that fails every
# probe without routes (~35s) plus the failure backoff and the post-tracking
# down interval -- so the tests only started counting towards tries_up long
# after the link was back (issue #4047, bench case 12e).
# The tracker now restores the routes the moment the carrier returns. That is
# a repair, not a test: the configured timing is untouched -- the backoff is
# still slept in full (failure_interval), the interface still needs tries_up
# consecutive successes, and post-tracking keeps its post_interval_down pace.
# The routes are simply there when the next scheduled test runs.
# _carrier_returned is true once, on the first call that sees the carrier up
# again after one that saw it down (or the device gone). A carrier that never
# dropped (upstream outage) never makes it true.
_carrier_returned() {
	local _c=
	read -r _c 2>/dev/null < "/sys/class/net/${OMR_TRACKER_DEVICE}/carrier"
	if [ "$_c" != "1" ]; then
		_omr_link_lost=1
		return 1
	fi
	[ -n "$_omr_link_lost" ] || return 1
	_omr_link_lost=
	return 0
}

# The same repair 001-initialize does ("Routes not correctly set"), run only
# when the interface table really lost the device's routes, and on the same
# interfaces: the tunnels are not WANs, they have no table of their own and
# must never go through an mptcp reload.
_on_carrier_return() {
	local _metric
	case "$OMR_TRACKER_INTERFACE" in
		omrvpn|omr6in4|"") return 0 ;;
	esac
	_metric="$(uci -q get network.${OMR_TRACKER_INTERFACE}.metric)"
	[ -n "$_metric" ] || return 0
	[ -z "$(ip route show table "$_metric" dev "$OMR_TRACKER_DEVICE" 2>/dev/null)" ] || return 0
	logger -t "omr-tracker" "$OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE) link is back, restoring its routes"
	/etc/init.d/mptcp reload "$OMR_TRACKER_DEVICE" >/dev/null 2>&1
}

# The failure backoff (OMR_TRACKER_FAILURE_INTERVAL), slept in full but in
# one-second steps, so a carrier returning during it gets its routes back
# right away rather than at the next cycle (see above).
_failure_sleep() {
	local _left="$1"
	while [ "$_left" -gt 0 ] 2>/dev/null; do
		_carrier_returned && _on_carrier_return
		sleep 1
		_left=$((_left - 1))
	done
}

# Each hook runs in the command substitution that collects its output (with
# the default TERM and INT handling, as traps are reset there), and the
# commands it printed run in a subshell of their own, only when there are
# any. Both used to run inside one more subshell: two forks per hook and per
# run instead of one, with about 17 hooks.
_post_tracking() {
	local EVAL_OUTPUT
	_log() {
		logger -t "post-tracking-${tracker_bin##*/}" "$*"
	}
	for tracker_bin in /usr/share/omr/post-tracking.d/*; do
		[ -x "$tracker_bin" ] || continue
		EVAL_OUTPUT=$(. "$tracker_bin" 2>&1)
		[ -n "$EVAL_OUTPUT" ] || continue
		(
			trap 'exit 0' TERM INT
			eval "$EVAL_OUTPUT" >/dev/null 2>&1
		)
	done
}

# _glorytun_udp_path_status <local address>: the "glorytun-udp" tracking
# type. OK when any path from this address is running: post-tracking.d/004
# adds one path per server IP, backup servers included, and those stay
# probing while the tunnel uses the current server, so the status of all
# the paths concatenated ("runningprobing") never matched "running"
_glorytun_udp_path_status() {
	local addr="$1" gtudp_states
	gtudp_states="$(glorytun-udp path addr "$addr" 2>/dev/null | awk -v a="$addr" '
		$1 == "path" && $2 == a { s = s (s == "" ? "" : ", ") $4; if ($4 == "running") r = 1 }
		END { if (r) print "running"; else print s }')"
	if [ "$gtudp_states" = "running" ]; then
		OMR_TRACKER_STATUS_MSG=""
		OMR_TRACKER_STATUS="OK"
	elif [ -z "$gtudp_states" ]; then
		OMR_TRACKER_STATUS_MSG="no Glorytun-UDP path from $addr ($OMR_TRACKER_INTERFACE)"
		OMR_TRACKER_STATUS="ERROR"
	else
		OMR_TRACKER_STATUS_MSG="Glorytun-UDP path $addr ($OMR_TRACKER_INTERFACE) is $gtudp_states"
		OMR_TRACKER_STATUS="ERROR"
	fi
}

# _ping_server <server> <device> [<localip>]: localip "no" for another WAN's
# device, whose latency and loss are not this interface's
_ping_server() {
	local servername="$1"
	[ -z "$servername" ] && return
	local disabled=$(uci -q get openmptcprouter."$servername".disabled)
	local device="$2"
	local localip="${3:-yes}"
	if [ -n "$device" ] && [ "$disabled" != "1" ]; then
		check_ping() {
			serverip="$1"
			if [ -n "$serverip" ]; then
				_ping "$serverip" "$device" "$localip"
				statusp=$?
				if [ "$statusp" -eq 0 ]; then
					serverip_ping=true
					return 0
				fi
			fi
		}
		config_load openmptcprouter
		config_list_foreach "${servername}" ip check_ping
	fi
}

# Set servers=true when at least one enabled server has an IP defined
count_server() {
	config_get disabled $1 disabled
	config_get serverip $1 ip
	if [ "$disabled" != "1" ] && [ -n "$serverip" ]; then
		servers=true
	fi
}

# The WANs in default and in backup mode, for the server test. `multipath`
# is a shell script whose listing forks per network device, and it ran twice
# per cycle (default, then backup) for as long as the VPS dropped this WAN's
# probes: one listing gives both, kept 30 s as in omr-tracker-server. A WAN
# switched since is seen up to 30 s late, a stale one only fails its probe.
_mp_default_intfs=""
_mp_backup_intfs=""
_mp_intfs_ts=""
_server_mp_intfs() {
	local _now _lists
	printf -v _now '%(%s)T' -1
	[ -n "$_mp_intfs_ts" ] && [ $((_now - _mp_intfs_ts)) -lt 30 ] && [ $((_now - _mp_intfs_ts)) -ge 0 ] && return
	_lists="$(multipath 2>/dev/null | awk '/ is in default mode/ { d = d " " $1 } / is in backup mode/ { b = b " " $1 } END { print d "|" b }')"
	_mp_default_intfs="${_lists%%|*}"
	_mp_backup_intfs="${_lists#*|}"
	_mp_intfs_ts="$_now"
}

# _server_all_intfs <probe function> <default|backup>: probe the servers
# through the other WANs of that mode until one answers. Their address is
# given for httping, which binds to it (-y), their device for ping. Done
# from here, a WAN without a bind address set NO_BIND for this tracker.
_server_all_intfs() {
	local _fn="$1" _mode="$2" _intf _intfs _ip _ignore _no_bind="$OMR_TRACKER_NO_BIND"
	_server_mp_intfs
	if [ "$_mode" = "backup" ]; then
		_intfs="$_mp_backup_intfs"
	else
		_intfs="$_mp_default_intfs"
	fi
	for _intf in $_intfs; do
		# this WAN's own probe has just failed
		[ "$_intf" = "$OMR_TRACKER_DEVICE" ] && continue
		if [ "$_fn" = "_httping_server" ]; then
			read -r _ignore _ignore _ip _ignore <<<"$(ip -4 -br addr ls dev "$_intf" 2>/dev/null)"
			_ip="${_ip%%/*}"
			[ -n "$_ip" ] || continue
			config_foreach "$_fn" server "$_ip" no
		else
			config_foreach "$_fn" server "$_intf" no
		fi
		[ "$serverip_ping" = true ] && break
	done
	OMR_TRACKER_NO_BIND="$_no_bind"
}

# Whether another WAN still reaches the servers: backup-mode WANs count too,
# or with the server answering only through one, this WAN was kept OK. They
# are only tried when no default-mode WAN answered, with a single probe:
# with the server down each of them made this cycle much longer.
_server_all() {
	local _fn="$1" _count="$OMR_TRACKER_COUNT"
	config_load openmptcprouter
	config_foreach count_server server
	[ "$servers" = true ] || return
	_server_all_intfs "$_fn" default
	[ "$serverip_ping" = true ] && return
	OMR_TRACKER_COUNT=1
	_server_all_intfs "$_fn" backup
	OMR_TRACKER_COUNT="$_count"
}

_ping_server_all() {
	_server_all _ping_server
}

# _httping_server <server> <local address> [<localip>]
_httping_server() {
	local servername="$1"
	[ -z "$servername" ] && return
	local disabled=$(uci -q get openmptcprouter."$servername".disabled)
	local port=$(uci -q get openmptcprouter."$servername".port)
	local device="$2"
	local localip="${3:-yes}"
	if [ -n "$device" ] && [ "$disabled" != "1" ]; then
		check_ping() {
			serverip="$1"
			if [ -n "$serverip" ]; then
				_httping "${serverip}:${port}" "$device" "$localip" true
				statusp=$?
				if [ "$statusp" -eq 0 ]; then
					serverip_ping=true
					return 0
				fi
			fi
		}
		config_load openmptcprouter
		config_list_foreach "${servername}" ip check_ping
	fi
}

_httping_server_all() {
	_server_all _httping_server
}

_parse_ping_output() {
	local output="$1"
	PING_LOSS=
	PING_LATENCY=
	PING_JITTER=
	PING_RTT_MIN=
	PING_RTT_MAX=
	# awk reads the output from a here-string: no printf in a pipeline, so
	# one process per ping instead of three
	IFS='|' read -r PING_LOSS PING_LATENCY PING_JITTER PING_RTT_MIN PING_RTT_MAX <<-EOF
$(awk '
/packet loss/ {
	# the field ending with "%": "+N errors" or "+N duplicates" come before
	# it, and iputils prints a fraction (33.3333% for 1 lost out of 3)
	for (i = 1; i <= NF; i++) {
		if ($i ~ /%$/) {
			sub("%", "", $i)
			loss = int($i)
			have_loss = 1
		}
	}
}
/rtt/ {
	n=split($0, parts, "/")
	split(parts[4], prefix, "= ")
	min=int(prefix[2])
	avg=int(parts[5])
	max=int(parts[6])
	jitter=sprintf("%.1f", parts[7])
}
END {
	if (have_loss) {
		printf "%s|%s|%s|%s|%s\n", loss, avg, jitter, min, max
	}
}' <<<"$output")
EOF
}

_apply_ping_metrics() {
	local localip="$1"
	[ "$localip" = "yes" ] || return 0
	[ -n "$PING_LATENCY" ] && OMR_TRACKER_LATENCY="$PING_LATENCY"
	[ -n "$PING_JITTER" ] && OMR_TRACKER_JITTER="$PING_JITTER"
	[ -n "$PING_RTT_MIN" ] && OMR_TRACKER_RTT_MIN="$PING_RTT_MIN"
	[ -n "$PING_RTT_MAX" ] && OMR_TRACKER_RTT_MAX="$PING_RTT_MAX"
	OMR_TRACKER_LOSS="$PING_LOSS"
}

_parse_httping_output() {
	local output="$1"
	HTTPING_LOSS=$(printf '%s\n' "$output" | awk '/failed/ {gsub("%",""); print int($5)}' | tr -d '\n')
	HTTPING_LATENCY=$(printf '%s\n' "$output" | cut -d "/" -s -f5 | cut -d "." -f1 | tr -d '\n')
}

_apply_httping_metrics() {
	local localip="$1"
	[ "$localip" = "yes" ] || return 0
	[ -n "$HTTPING_LATENCY" ] && OMR_TRACKER_LATENCY="$HTTPING_LATENCY"
	OMR_TRACKER_LOSS="$HTTPING_LOSS"
}

# A probe runs in the background and is waited for, with its output in $ret:
# bash runs a trap only once the command it runs in the foreground is done,
# so on TERM the tracker went on with the ping up to its deadline (20 s for
# omrvpn), while "wait" returns at once and _omr_exit kills the probe.
_probe_out="/tmp/omr-tracker-${OMR_TRACKER_INTERFACE}.probe"
_probe() {
	"$@" >"$_probe_out" 2>&1 &
	wait $!
	IFS= read -r -d '' ret <"$_probe_out"
}

_ping() {
	local host="$1"
	[ -z "$host" ] && return
	local device="$2"
	local localip="$3"
	if [ -n "$OMR_TRACKER_INTERFACE" ] && { [ "$OMR_TRACKER_NO_BIND" = "1" ] || [ "$OMR_TRACKER_INTERFACE_PROTO" = "3g" ] || [ "$OMR_TRACKER_INTERFACE_PROTO" = "qmi" ] || [ "$OMR_TRACKER_INTERFACE_PROTO" = "ncm" ]; }; then
		_probe ping -I "${device}" \
			-w "$OMR_TRACKER_TIMEOUT" \
			-c "$OMR_TRACKER_COUNT" \
			-s "$OMR_TRACKER_SIZE" \
			-t "$OMR_TRACKER_MAX_TTL" \
			-Q 184 \
			"${host}"
	else
		_probe ping -B -I "${device}" \
			-w "$OMR_TRACKER_TIMEOUT" \
			-c "$OMR_TRACKER_COUNT" \
			-s "$OMR_TRACKER_SIZE" \
			-t "$OMR_TRACKER_MAX_TTL" \
			-Q 184 \
			"${host}"
		case "$ret" in
			*"Address not available"*) OMR_TRACKER_NO_BIND=1 ;;
		esac
	fi
	_parse_ping_output "$ret"
	if [ -n "$PING_LOSS" ] && [ "$PING_LOSS" != "100" ]; then
		_apply_ping_metrics "$localip"
		return 0
	fi
	return 1
}
#'
_httping() {
	local host=$1
	local deviceip=$2
	local localip=$3
	local https=$4
	local flag=""
	[ "$https" = "true" ] && flag="-l"
	_probe httping $flag "${host}" \
		-y "${deviceip}" \
		-t "$OMR_TRACKER_TIMEOUT" \
		-c "$OMR_TRACKER_COUNT"
	case "$ret" in
		*"Address not available"*) OMR_TRACKER_NO_BIND=1 ;;
	esac
	_parse_httping_output "$ret"
	if [ -n "$HTTPING_LOSS" ] && [ "$HTTPING_LOSS" != "100" ]; then
		_apply_httping_metrics "$localip"
		return 0
	fi
	return 1
}

_dns() {
	local host=$1
	local deviceip=$2
	ret=$(dig @"${host}" \
		-b "${deviceip}" \
		    +time="$OMR_TRACKER_TIMEOUT" \
		    +tries="$OMR_TRACKER_COUNT" \
		    one.one.one.one
	) && echo "$ret" | grep -sq "1.1.1.1" && {
		OMR_TRACKER_LATENCY=$(echo "$ret" | awk '/Query time/{print $4}')
		#_update_rto "$OMR_TRACKER_LATENCY"
		return 0
	}
	return 1
}

_none() {
	return 0
}

# Export callback functions for config_foreach
export -f count_server
export -f _ping_server
export -f _ping_server_all
export -f _httping_server
export -f _httping_server_all

_restart

OMR_TRACKER_INTERFACE_PROTO="$(uci -q get network.${OMR_TRACKER_INTERFACE}.proto)"
case "$OMR_TRACKER_INTERFACE_PROTO" in
	3g|qmi|ncm) _probe_route=1 ;;
	*) _probe_route="" ;;
esac
OMR_TRACKER_PREV_STATUS=""
# A tracker (re)started while its WAN is still marked down (omr-tracker
# restart, a reload for a settings change) has no previous status: its first
# good probe made it OK at once, without the tries_up streak or the recovery
# thresholds. Its first cycle is taken as following an ERROR for those, and
# for the hooks while the streak holds it or it recovers (see the streak
# gate): on a failure they get no previous status, so that 002-error applies
# it again. Monitor only never sets it down.
_was_down_at_start() {
	[ "$OMR_TRACKER_MONITOR_ONLY" != "1" ] && \
		[ "$(uci -q -P /var/state get "openmptcprouter.${OMR_TRACKER_INTERFACE}.state")" = "down" ]
}
_started_down=""
_was_down_at_start && _started_down=1
# The modem may still be initializing: wait for netifd to report the
# interface up instead of sleeping blindly, with the configured delay
# (default 120s) as the cap. Tracking — and with it MPTCP endpoint setup —
# then starts as soon as the connection is established.
[ "$OMR_TRACKER_INTERFACE_PROTO" = "modemmanager" ] && {
	_mm_delay=$(uci -q get network.${OMR_TRACKER_INTERFACE}.delay || echo '120')
	while [ "$_mm_delay" -gt 0 ]; do
		[ "$(ubus call network.interface."$OMR_TRACKER_INTERFACE" status 2>/dev/null | jsonfilter -q -e '@.up')" = "true" ] && break
		sleep 2
		_mm_delay=$((_mm_delay - 2))
	done
}

# Whether this interface's "gateway" is actually a remote endpoint, which
# decides if the liveness ping below is worth measuring (see _ping's third
# argument and _apply_ping_metrics). For a WAN it is not: the gateway is one
# hop away on the ISP side, so its RTT says nothing about the path, and
# quality is taken from the real target instead (VPS IP, else latency_ip,
# else the hosts list). The omrvpn/OWVPN* tunnel is the exception -- its
# gateway is the far end of the tunnel on the VPS, so that one ping already
# crosses the whole aggregated path and carries the tunnel's real latency,
# jitter and loss. Those interfaces are tracked with type "none", which
# breaks out of the loop before any target probe runs, so this ping is the
# only measurement they will ever get -- keep it instead of discarding it.
case "$OMR_TRACKER_INTERFACE" in
	omrvpn|OWVPN*) OMR_TRACKER_GATEWAY_IS_REMOTE="yes" ;;
	*)             OMR_TRACKER_GATEWAY_IS_REMOTE="no" ;;
esac

_host4_raw=""
_host4_resolved=""
_host4_resolve_time=0
_host6_raw=""
_host6_resolved=""
_host6_resolve_time=0
_post_tracking_last=0
# Device the previous cycle actually tracked, so a change (a PPP session
# coming up and replacing the L2 base device, a modem re-enumerating, ...)
# can be noticed and the per-device caches dropped. Seeded with what the
# init script resolved at launch time, so moving off a device that was
# already stale when this process started is reported too.
_tracked_device="$OMR_TRACKER_DEVICE"
# Consecutive successful cycles seen so far while recovering from ERROR; see
# the tries_up gate below.
OMR_TRACKER_UP_STREAK=0

# main loop
while true; do
	# the carrier may also come back while the previous cycle was probing
	_carrier_returned && _on_carrier_return
	# setup tracker variables
	OMR_TRACKER_DEVICE_IP=
	OMR_TRACKER_DEVICE_IP6=
	OMR_TRACKER_STATUS="ERROR"
	OMR_TRACKER_STATUS_MSG=""
	OMR_TRACKER_LATENCY=
	OMR_TRACKER_LOSS=
	OMR_TRACKER_JITTER=
	OMR_TRACKER_RTT_MIN=
	OMR_TRACKER_RTT_MAX=
	#OMR_TRACKER_TIMEOUT=$((rto / 1000 + (rto % 1000 ? 1 : 0)))
	# Both lists are only of this cycle's failed hosts: a list never reset
	# grew on every failed probe and went into the exported status message,
	# until it was past the size of the environment and every command the
	# tracker ran failed with "Argument list too long"
	OMR_TRACKER_LIST_HOSTS=""
	OMR_TRACKER_LIST_HOSTS6=""
	# Reuse the last known gateways while the link is healthy: rediscovery
	# costs ~15 processes per cycle. A stale gateway makes the next ping
	# fail, flipping status to ERROR, which forces a full rediscovery on
	# the following cycle.
	# A gateway that stopped answering while the hosts still did leaves the
	# status OK, so it is also looked up again then (a DHCP renumbering left
	# the table 991337 default route on the old one), once per address:
	# some gateways never answer a ping.
	if [ "$OMR_TRACKER_PREV_STATUS" = "OK" ]; then
		OMR_TRACKER_DEVICE_GATEWAY="$OMR_TRACKER_PREV_GATEWAY"
		OMR_TRACKER_DEVICE_GATEWAY6="$OMR_TRACKER_PREV_GATEWAY6"
		if [ -n "$_gw4_failed" ] && [ "$_gw4_failed" != "$_gw4_relooked" ]; then
			_gw4_relooked="$_gw4_failed"
			OMR_TRACKER_DEVICE_GATEWAY=
		fi
		if [ -n "$_gw6_failed" ] && [ "$_gw6_failed" != "$_gw6_relooked" ]; then
			_gw6_relooked="$_gw6_failed"
			OMR_TRACKER_DEVICE_GATEWAY6=
		fi
	else
		OMR_TRACKER_DEVICE_GATEWAY=
		OMR_TRACKER_DEVICE_GATEWAY6=
	fi
	_gw4_failed=""
	_gw6_failed=""
	serverip_ping=false
	OMR_TRACKER_PINGED=""
	# The VPS is the latency reference when its IP is defined, the custom
	# hosts list is only used to measure latency when no VPS is set. Cache
	# the answer as scanning the server sections costs several forks.
	printf -v _server_check_now '%(%s)T' -1
	if [ -z "$_server_check_time" ] || [ $((_server_check_now - _server_check_time)) -gt 300 ]; then
		servers=false
		config_load openmptcprouter
		config_foreach count_server server
		OMR_TRACKER_SERVER_DEFINED=$servers
		_server_check_time=$_server_check_now
	fi
	if [ "$OMR_TRACKER_SERVER_DEFINED" = true ]; then
		host_localip="no"
	else
		host_localip="yes"
	fi
	# When no VPS is defined, a dedicated custom IP (latency_ip/latency_ip6)
	# replaces the hosts list as check target; the hosts list is restored
	# when a VPS appears or when no custom IP is set.
	if [ "$OMR_TRACKER_SERVER_DEFINED" != true ] && [ -n "$OMR_TRACKER_LATENCY_IP" ]; then
		if [ "$_custom4_active" != "1" ]; then
			_hosts4_saved="$OMR_TRACKER_HOSTS"
			OMR_TRACKER_HOSTS="$OMR_TRACKER_LATENCY_IP"
			OMR_TRACKER_HOST="$OMR_TRACKER_LATENCY_IP"
			_custom4_active=1
		fi
	elif [ "$_custom4_active" = "1" ]; then
		OMR_TRACKER_HOSTS="$_hosts4_saved"
		OMR_TRACKER_HOST="${OMR_TRACKER_HOSTS%% *}"
		_custom4_active=0
	fi
	if [ "$OMR_TRACKER_SERVER_DEFINED" != true ] && [ -n "$OMR_TRACKER_LATENCY_IP6" ]; then
		if [ "$_custom6_active" != "1" ]; then
			_hosts6_saved="$OMR_TRACKER_HOSTS6"
			OMR_TRACKER_HOSTS6="$OMR_TRACKER_LATENCY_IP6"
			OMR_TRACKER_HOST6="$OMR_TRACKER_LATENCY_IP6"
			_custom6_active=1
		fi
	elif [ "$_custom6_active" = "1" ]; then
		OMR_TRACKER_HOSTS6="$_hosts6_saved"
		OMR_TRACKER_HOST6="${OMR_TRACKER_HOSTS6%% *}"
		_custom6_active=0
	fi
	if [ "$(uci -q get network.${OMR_TRACKER_INTERFACE}.disabled)" = "1" ]; then
		sleep "${OMR_TRACKER_INTERVAL:-2}"
		continue
	fi
	# One ubus call per cycle answers both questions the loop below asks of
	# netifd: is the interface up, and what is its L3 device right now.
	# jsonfilter evaluates the expressions in order and silently drops the
	# ones that do not match; "up" is always present on a configured
	# interface, so the first line is always the up state and the second one
	# (when there is one) the device. ubus directly: /sbin/ifstatus is a
	# shell wrapper that costs two extra processes (sh + "ubus list") per
	# call.
	_iface_status=$(ubus call network.interface."$OMR_TRACKER_INTERFACE" status 2>/dev/null | jsonfilter -q -e '@.up' -e '@["l3_device"]')
	_iface_up="${_iface_status%%$'\n'*}"
	if [ "$_iface_up" = "$_iface_status" ]; then
		_iface_l3dev=""
	else
		_iface_l3dev="${_iface_status#*$'\n'}"
	fi
	# "dev@parent" is not a usable device name: treat it as no answer so the
	# fallback chain below resolves a real one.
	[[ "$_iface_l3dev" == *@* ]] && _iface_l3dev=""

	# The L3 device is not fixed for the lifetime of a tracker. PPP family
	# protocols (pppoe, pptp, l2tp, ...) only create their pppoe-<intf>
	# device once the session is established, and until then netifd reports
	# just the L2 base device ("device": "eth3.835", no "l3_device"), which
	# is what the fallback chain below has to settle for.
	if [ -n "$_iface_l3dev" ]; then
		OMR_TRACKER_DEVICE="$_iface_l3dev"
	elif [ -z "$OMR_TRACKER_DEVICE" ] || [ ! -d "/sys/class/net/$OMR_TRACKER_DEVICE" ]; then
		OMR_TRACKER_DEVICE=$(ubus call network.interface."${OMR_TRACKER_INTERFACE}_4" status 2>/dev/null | jsonfilter -q -e '@["l3_device"]')
		#[ -z "$OMR_TRACKER_DEVICE" ] && config_get OMR_TRACKER_DEVICE "$OMR_TRACKER_INTERFACE" device
		[[ "$OMR_TRACKER_DEVICE" == *@* ]] && OMR_TRACKER_DEVICE=$(ubus call network.interface."$OMR_TRACKER_INTERFACE" status 2>/dev/null | jsonfilter -q -e '@["device"]')
		[ -z "$OMR_TRACKER_DEVICE" ] && network_get_physdev OMR_TRACKER_DEVICE "$OMR_TRACKER_INTERFACE"
		[ -z "$OMR_TRACKER_DEVICE" ] && network_get_device OMR_TRACKER_DEVICE "$OMR_TRACKER_INTERFACE"
	fi

	# Everything cached from the previous cycle describes the device we were
	# tracking then, so drop it when the interface moved to another one.
	if [ "$OMR_TRACKER_DEVICE" != "$_tracked_device" ]; then
		[ -n "$_tracked_device" ] && [ -n "$OMR_TRACKER_DEVICE" ] && \
			logger -t "omr-tracker" "$OMR_TRACKER_INTERFACE switched device from $_tracked_device to $OMR_TRACKER_DEVICE"
		_tracked_device="$OMR_TRACKER_DEVICE"
		OMR_TRACKER_DEVICE_GATEWAY=
		OMR_TRACKER_DEVICE_GATEWAY6=
		OMR_TRACKER_DEVICE_ROUTE=
	fi

	if [ -n "$OMR_TRACKER_DEVICE" ] && [ -d "/sys/class/net/$OMR_TRACKER_DEVICE" ]; then
		OMR_TRACKER_PREV_DEVICE="$OMR_TRACKER_DEVICE"
		_device_state_var OMR_TRACKER_DEVICE_STATE "$OMR_TRACKER_DEVICE"
		if { [ "$_iface_up" = "true" ] && [ "$OMR_TRACKER_DEVICE_STATE" != "DOWN" ]; } || [ "$OMR_TRACKER_INTERFACE" = "omrvpn" ]; then
			# retrieve iface ip and gateway
			if { [ "$OMR_TRACKER_FAMILY" = "ipv4" ] || [ "$OMR_TRACKER_FAMILY" = "ipv4ipv6" ]; } && [ "$OMR_TRACKER_INTERFACE_PROTO" != "dhcpv6" ]; then
				# "<dev> <state> <addr>/<len> ...": the first address, read
				# without awk, i.e. one process per cycle instead of three
				read -r _ignore _ignore OMR_TRACKER_DEVICE_IP _ignore <<<"$(ip -4 -br addr ls dev "$OMR_TRACKER_DEVICE")"
				OMR_TRACKER_DEVICE_IP="${OMR_TRACKER_DEVICE_IP%%/*}"
				if [ -z "$OMR_TRACKER_DEVICE_IP" ]; then
					OMR_TRACKER_DEVICE_IP=$(ip -4 addr show dev "$OMR_TRACKER_DEVICE" | grep -m 1 inet | awk '{print $2}' | cut -d'/' -s -f1)
				fi
				# a new address may come with a new gateway
				[ "$OMR_TRACKER_DEVICE_IP" != "$OMR_TRACKER_PREV_DEVICE_IP" ] && OMR_TRACKER_DEVICE_GATEWAY=
				#OMR_TRACKER_DEVICE_IP=$(ubus call network.interface.$OMR_TRACKER_INTERFACE status | jsonfilter -q -e '@["ipv4-address"][0].address' | tr -d "\n")
				#if [ -z "$OMR_TRACKER_DEVICE_GATEWAY" ]; then
				#	OMR_TRACKER_DEVICE_GATEWAY=$(ip -4 r list dev "$OMR_TRACKER_DEVICE" | grep -v default | awk '/proto static/ {print $1}' | tr -d "\n")
				#fi
				if [ "$OMR_TRACKER_DEVICE" = "bonding-omrvpn" ]; then
					OMR_TRACKER_DEVICE_GATEWAY="10.255.248.1"
				fi
				if [ -z "$OMR_TRACKER_DEVICE_GATEWAY" ]; then
					OMR_TRACKER_DEVICE_GATEWAY=$(uci -q get "network.$OMR_TRACKER_INTERFACE.gateway")
				fi
				if [ -z "$OMR_TRACKER_DEVICE_GATEWAY" ] || [ "$OMR_TRACKER_DEVICE_GATEWAY" = "0.0.0.0" ]; then
					OMR_TRACKER_DEVICE_GATEWAY=$(ubus call network.interface.$OMR_TRACKER_INTERFACE status | jsonfilter -q -l 1 -e '@.inactive.route[@.target="0.0.0.0"].nexthop' | tr -d "\n")
				fi
				if [ -z "$OMR_TRACKER_DEVICE_GATEWAY" ] || [ "$OMR_TRACKER_DEVICE_GATEWAY" = "0.0.0.0" ]; then
					OMR_TRACKER_DEVICE_GATEWAY=$(ubus call network.interface.$OMR_TRACKER_INTERFACE status | jsonfilter -q -l 1 -e '@.route[@.target="0.0.0.0"].nexthop' | tr -d "\n")
				fi
				if [ -z "$OMR_TRACKER_DEVICE_GATEWAY" ] || [ "$OMR_TRACKER_DEVICE_GATEWAY" = "0.0.0.0" ]; then
					OMR_TRACKER_DEVICE_GATEWAY=$(ubus call network.interface.${OMR_TRACKER_INTERFACE}_4 status 2>/dev/null | jsonfilter -q -l 1 -e '@.inactive.route[@.target="0.0.0.0"].nexthop' | tr -d "\n")
				fi
				if [ -z "$OMR_TRACKER_DEVICE_GATEWAY" ] || [ "$OMR_TRACKER_DEVICE_GATEWAY" = "0.0.0.0" ]; then
					if [ "$OMR_TRACKER_INTERFACE" = "omrvpn" ] && [ "$(uci -q get glorytun.vpn.enable)" = "1" ]; then
						OMR_TRACKER_DEVICE_GATEWAY=$(uci -q get glorytun.vpn.remoteip)
					elif [ "$OMR_TRACKER_INTERFACE" = "omrvpn" ] && [ "$(uci -q get glorytun-udp.vpn.enable)" = "1" ]; then
						OMR_TRACKER_DEVICE_GATEWAY=$(uci -q get glorytun-udp.vpn.remoteip)
					else
						OMR_TRACKER_DEVICE_GATEWAY=""
					fi
				fi
				if [ -z "$OMR_TRACKER_DEVICE_GATEWAY" ]; then
					OMR_TRACKER_DEVICE_ROUTE=$(ip -4 r list dev "$OMR_TRACKER_DEVICE" | grep via | grep -v default | grep -v nexthop | grep -v metric | grep -v / | awk '{print $1; exit}' | tr -d "\n")
				fi
				if [ -z "$OMR_TRACKER_DEVICE_GATEWAY" ]; then
					OMR_TRACKER_DEVICE_GATEWAY=$(ip -4 r list dev "$OMR_TRACKER_DEVICE" | grep kernel | grep -v '.0/' | awk '/proto kernel/ {print $1;exit}' | tr -d "\n")
				fi
				if [ -z "$OMR_TRACKER_DEVICE_GATEWAY" ]; then
					OMR_TRACKER_DEVICE_GATEWAY=$(ip -4 r list dev "$OMR_TRACKER_DEVICE" | grep -m 1 default | awk '/via/ {print $3;exit}' | tr -d "\n")
				fi
				if [ -z "$OMR_TRACKER_DEVICE_GATEWAY" ]; then
					OMR_TRACKER_DEVICE_GATEWAY=$(ip -4 r list dev "$OMR_TRACKER_DEVICE" | awk '/via/ {print $3;exit}' | tr -d "\n")
				fi
				if [ -z "$OMR_TRACKER_DEVICE_GATEWAY" ] && [ "$OMR_TRACKER_INTERFACE" = "omrvpn" ] && [ "$(uci -q get openvpn.omr.enabled)" = "1" ]; then
					OMR_TRACKER_DEVICE_GATEWAY="10.255.252.1"
				fi

			fi
			if { [ "$OMR_TRACKER_IPV6" = "1" ] || [ "$OMR_TRACKER_IPV6" = "auto" ]; } && { [ "$OMR_TRACKER_FAMILY" = "ipv6" ] || [ "$OMR_TRACKER_FAMILY" = "ipv4ipv6" ]; }; then
				#OMR_TRACKER_DEVICE_IP6=$(ip -6 -br addr ls dev "$OMR_TRACKER_DEVICE" | awk -F'[ /]+' '{print $3}')
				#if [ -z "$OMR_TRACKER_DEVICE_IP6" ]; then
					_device_ip6_var OMR_TRACKER_DEVICE_IP6 "$OMR_TRACKER_DEVICE"
				#fi
				[ "$OMR_TRACKER_DEVICE_IP6" != "$OMR_TRACKER_PREV_DEVICE_IP6" ] && OMR_TRACKER_DEVICE_GATEWAY6=
				# without a global IPv6 address no IPv6 probe can run, so
				# don't burn ubus/jsonfilter calls looking for a gateway
				if [ -n "$OMR_TRACKER_DEVICE_IP6" ]; then
					if [ -z "$OMR_TRACKER_DEVICE_GATEWAY6" ]; then
						OMR_TRACKER_DEVICE_GATEWAY6=$(uci -q get "network.$OMR_TRACKER_INTERFACE.ip6gw")
					fi
					if [ -z "$OMR_TRACKER_DEVICE_GATEWAY6" ] || [ "$OMR_TRACKER_DEVICE_GATEWAY6" = "::" ]; then
						OMR_TRACKER_DEVICE_GATEWAY6=$(ubus call network.interface.${OMR_TRACKER_INTERFACE} status 2>/dev/null | jsonfilter -q -l 1 -e "@.inactive.route[@.source=\"${OMR_TRACKER_DEVICE_IP6}\"].nexthop" | tr -d "\n")
					fi
					if [ -z "$OMR_TRACKER_DEVICE_GATEWAY6" ] || [ "$OMR_TRACKER_DEVICE_GATEWAY6" = "::" ]; then
						OMR_TRACKER_DEVICE_GATEWAY6=$(ubus call network.interface.${OMR_TRACKER_INTERFACE} status 2>/dev/null | jsonfilter -q -l 1 -e "@.inactive.route[@.source=\"${OMR_TRACKER_DEVICE_IP6}/64\"].nexthop" | tr -d "\n")
					fi
					if [ -z "$OMR_TRACKER_DEVICE_GATEWAY6" ] || [ "$OMR_TRACKER_DEVICE_GATEWAY6" = "::" ]; then
						OMR_TRACKER_DEVICE_GATEWAY6=$(ubus call network.interface.${OMR_TRACKER_INTERFACE} status 2>/dev/null | jsonfilter -q -l 1 -e "@.inactive.route[@.source=\"${OMR_TRACKER_DEVICE_IP6}/56\"].nexthop" | tr -d "\n")
					fi
					if [ -z "$OMR_TRACKER_DEVICE_GATEWAY6" ] || [ "$OMR_TRACKER_DEVICE_GATEWAY6" = "::" ]; then
						OMR_TRACKER_DEVICE_GATEWAY6=$(ubus call network.interface.$OMR_TRACKER_INTERFACE status | jsonfilter -q -l 1 -e '@.route[@.target="::"].nexthop' | tr -d "\n")
					fi
					# on a DHCP WAN the IPv6 default route belongs to the companion <intf>_6 DHCPv6 interface
					if [ -z "$OMR_TRACKER_DEVICE_GATEWAY6" ] || [ "$OMR_TRACKER_DEVICE_GATEWAY6" = "::" ]; then
						OMR_TRACKER_DEVICE_GATEWAY6=$(ubus call network.interface.${OMR_TRACKER_INTERFACE}_6 status 2>/dev/null | jsonfilter -q -l 1 -e '@.inactive.route[@.target="::"].nexthop' | tr -d "\n")
					fi
					if [ -z "$OMR_TRACKER_DEVICE_GATEWAY6" ] || [ "$OMR_TRACKER_DEVICE_GATEWAY6" = "::" ]; then
						OMR_TRACKER_DEVICE_GATEWAY6=$(ubus call network.interface.${OMR_TRACKER_INTERFACE}_6 status 2>/dev/null | jsonfilter -q -l 1 -e '@.route[@.target="::"].nexthop' | tr -d "\n")
					fi
					# the default route whatever its source: without source routing
					# (sourcefilter 0) it has none, and it can have a prefix length
					# the lookups above don't try
					if [ -z "$OMR_TRACKER_DEVICE_GATEWAY6" ] || [ "$OMR_TRACKER_DEVICE_GATEWAY6" = "::" ]; then
						OMR_TRACKER_DEVICE_GATEWAY6=$(ubus call network.interface.$OMR_TRACKER_INTERFACE status | jsonfilter -q -l 1 -e '@.inactive.route[@.target="::"].nexthop' | tr -d "\n")
					fi
					# the host route to the gateway that some protocols add (ModemManager);
					# the target of any other on-link route is a prefix, not a router
					if [ -z "$OMR_TRACKER_DEVICE_GATEWAY6" ] || [ "$OMR_TRACKER_DEVICE_GATEWAY6" = "::" ]; then
						OMR_TRACKER_DEVICE_GATEWAY6=$(ubus call network.interface.$OMR_TRACKER_INTERFACE status | jsonfilter -q -l 1 -e '@.route[@.nexthop="::" && @.mask=128].target' | tr -d "\n")
					fi
				fi
			fi

			# execute specific tracker
			_ipv4_status=""
			if { [ "$OMR_TRACKER_FAMILY" = "ipv4" ] || [ "$OMR_TRACKER_FAMILY" = "ipv4ipv6" ]; } && [ -n "$OMR_TRACKER_DEVICE_IP" ] && [ -n "$OMR_TRACKER_DEVICE_GATEWAY" ]; then
				OMR_TRACKER_PINGED=1
				# setup loop variable
				if [ "$OMR_TRACKER_PREV_STATUS" = "ERROR" ]; then
					tries="$OMR_TRACKER_TRIES_UP"
				else
					tries="$OMR_TRACKER_TRIES"
				fi
				# loop until tries attempts have been reached
				while [ "$tries" -gt 0 ]; do
					if [ "$OMR_TRACKER_TYPE" = "glorytun-udp" ]; then
						_glorytun_udp_path_status "$OMR_TRACKER_DEVICE_IP"
						break
					fi
					if [ -n "$OMR_TRACKER_DEVICE_ROUTE" ]; then
						_ping "$OMR_TRACKER_DEVICE_ROUTE" "$OMR_TRACKER_DEVICE" "$OMR_TRACKER_GATEWAY_IS_REMOTE"
						status=$?
					else
						_ping "$OMR_TRACKER_DEVICE_GATEWAY" "$OMR_TRACKER_DEVICE" "$OMR_TRACKER_GATEWAY_IS_REMOTE"
						status=$?
						[ "$status" -ne 0 ] && _gw4_failed="$OMR_TRACKER_DEVICE_GATEWAY"
					fi
					if [ "$status" -eq 0 ] && [ "$OMR_TRACKER_TYPE" = "none" ]; then
						OMR_TRACKER_STATUS_MSG=""
						OMR_TRACKER_STATUS="OK"
						break
					elif [ "$OMR_TRACKER_TYPE" != "none" ]; then
						if [ "$status" -ne 0 ]; then
							OMR_TRACKER_STATUS_MSG="IPv4 gateway down"
						fi
						serverip_ping=false
						if [ "$OMR_TRACKER_SERVER_DEFINED" = true ]; then
							if [ "$OMR_TRACKER_TYPE" = "ping" ]; then
								config_load openmptcprouter
								config_foreach _ping_server server $OMR_TRACKER_DEVICE
							fi
							if [ "$OMR_TRACKER_TYPE" = "httping" ]; then
								config_load openmptcprouter
								config_foreach _httping_server server $OMR_TRACKER_DEVICE_IP
							fi
						fi
						if [ "$serverip_ping" = false ] && [ -n "$OMR_TRACKER_HOST" ]; then
							_resolve_host4
							if [ -n "$OMR_TRACKER_HOST" ]; then
								_probe_failed=""
								if [ -n "$_probe_route" ]; then
									# Check if route is not used
									_probe_tries=0
									while _is_ip "$OMR_TRACKER_HOST" && ! ip route add $OMR_TRACKER_HOST via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE src $OMR_TRACKER_DEVICE_IP $OMR_TRACKER_PROBE_ROUTE > /dev/null 2>&1
									do
										# The address and the gateway are only looked up again
										# on the next cycle: when the modem got new ones the add
										# could never work, and the tracker waited here forever
										_probe_tries=$((_probe_tries + 1))
										if [ "$_probe_tries" -ge 3 ]; then
											_probe_failed=1
											break
										fi
										logger -t "omr-tracker" "Can't create route to $OMR_TRACKER_HOST via $OMR_TRACKER_DEVICE_GATEWAY dev $OMR_TRACKER_DEVICE src $OMR_TRACKER_DEVICE_IP. waiting..."
										sleep 2
										ip route del "$OMR_TRACKER_HOST" via "$OMR_TRACKER_DEVICE_GATEWAY" dev "$OMR_TRACKER_DEVICE" $OMR_TRACKER_PROBE_ROUTE > /dev/null 2>&1
										_restart
										_resolve_host4
									done
								fi
								if [ -n "$_probe_failed" ]; then
									statusb=1
								elif [ "$OMR_TRACKER_TYPE" = "ping" ]; then
									_ping "$OMR_TRACKER_HOST" "$OMR_TRACKER_DEVICE" "$host_localip"
									statusb=$?
								elif [ "$OMR_TRACKER_TYPE" = "httping" ]; then
									_httping "$OMR_TRACKER_HOST" "$OMR_TRACKER_DEVICE_IP" "$host_localip"
									statusb=$?
								elif [ "$OMR_TRACKER_TYPE" = "dns" ]; then
									_dns "$OMR_TRACKER_HOST" "$OMR_TRACKER_DEVICE_IP" "$host_localip"
									statusb=$?
								fi
								[ -n "$_probe_route" ] && ip route del "$OMR_TRACKER_HOST" via "$OMR_TRACKER_DEVICE_GATEWAY" dev "$OMR_TRACKER_DEVICE" $OMR_TRACKER_PROBE_ROUTE > /dev/null 2>&1
								if [ "$statusb" -eq 0 ]; then
									#OMR_TRACKER_STATUS_MSG=""
									OMR_TRACKER_STATUS="OK"
								else
									if [ "$OMR_TRACKER_LIST_HOSTS" = "" ]; then
										OMR_TRACKER_LIST_HOSTS="$OMR_TRACKER_HOST"
									else
										OMR_TRACKER_LIST_HOSTS="$OMR_TRACKER_LIST_HOSTS,$OMR_TRACKER_HOST"
									fi
									OMR_TRACKER_STATUS_MSG="check error"
								fi
							fi
						else
							#OMR_TRACKER_STATUS_MSG=""
							OMR_TRACKER_STATUS="OK"
						fi
						if [ "$serverip_ping" != false ] && [ "$OMR_TRACKER_STATUS" = "OK" ] && [ "$OMR_TRACKER_TYPE" != "httping" ] && [ "$OMR_TRACKER_SERVER_HTTP_TEST" = "1" ]; then
							serverip_ping=false
							config_load openmptcprouter
							config_foreach _httping_server server $OMR_TRACKER_DEVICE_IP
							if [ "$serverip_ping" = false ]; then
								#OMR_TRACKER_STATUS="ERROR"
								OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_STATUS_MSG No access to server API"
							else
								OMR_TRACKER_STATUS_MSG=""
								OMR_TRACKER_STATUS="OK"
								break
							fi
						elif [ "$serverip_ping" = false ] && [ "$OMR_TRACKER_STATUS" = "OK" ] && [ "$OMR_TRACKER_SERVER_TEST" = "1" ]; then
							if [ "$OMR_TRACKER_TYPE" = "ping" ]; then
								_ping_server_all
							fi
							if [ "$OMR_TRACKER_TYPE" = "httping" ]; then
								_httping_server_all
							fi
							if [ "$serverip_ping" != false ] && [ "$servers" = true ]; then
								OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_STATUS_MSG No answer from server"
								OMR_TRACKER_STATUS="ERROR"
								break
							fi
						fi
						[ "$OMR_TRACKER_STATUS" = "OK" ] && break
					elif [ "$status" -ne 0 ]; then
						OMR_TRACKER_STATUS_MSG="IPv4 gateway down"
					fi
					tries=$((tries - 1))
					#_restart
					OMR_TRACKER_HOST="${OMR_TRACKER_HOSTS%% *}"
					[ "$OMR_TRACKER_HOST" = "$OMR_TRACKER_HOSTS" ] || {
						OMR_TRACKER_HOSTS="${OMR_TRACKER_HOSTS#* } $OMR_TRACKER_HOST"
					}
					#OMR_TRACKER_TIMEOUT=$((OMR_TRACKER_TIMEOUT * 2))
					sleep "${OMR_TRACKER_INTERVAL_TRIES:-1}"
				done
				_ipv4_status="$OMR_TRACKER_STATUS"
			fi
			#if [ "$(uci -q get openmptcprouter.settings.disable_ipv6)" = "0" ] && [ -n "$OMR_TRACKER_DEVICE_IP6" ] && [ -n "$OMR_TRACKER_DEVICE_GATEWAY6" ]; then
			# An interface whose IPv4 check failed stays down whatever IPv6
			# answers, or the IPv4 routes and subflows through it stay on a
			# dead path: the IPv6 checks could only delay the next cycle and
			# replace the IPv4 failure message with their own
			if [ "$_ipv4_status" != "ERROR" ] && [ -n "$OMR_TRACKER_DEVICE_IP6" ] && [ -n "$OMR_TRACKER_DEVICE_GATEWAY6" ]; then
				OMR_TRACKER_PINGED=1
				# setup loop variable
				if [ "$OMR_TRACKER_PREV_STATUS" = "ERROR" ]; then
					tries="$OMR_TRACKER_TRIES_UP"
				else
					tries="$OMR_TRACKER_TRIES"
				fi
				# loop until tries attempts have been reached
				while [ "$tries" -gt 0 ]; do
					# Without this the host probes below ran with no statusb for
					# this type: "check error" on every cycle, and a WAN tracked
					# in IPv6 only stayed in ERROR whatever its path did. The
					# tunnel uses any running path, so a running IPv4 path is
					# enough, and one from the IPv6 address otherwise
					if [ "$OMR_TRACKER_TYPE" = "glorytun-udp" ]; then
						[ "$_ipv4_status" = "OK" ] || _glorytun_udp_path_status "$OMR_TRACKER_DEVICE_IP6"
						break
					fi
					#if [ -n "$OMR_TRACKER_DEVICE_ROUTE" ]; then
					#	_ping "$OMR_TRACKER_DEVICE_ROUTE" "$OMR_TRACKER_DEVICE" "no"
					#	status=$?
					#else
						_ping "$OMR_TRACKER_DEVICE_GATEWAY6" "$OMR_TRACKER_DEVICE" "$OMR_TRACKER_GATEWAY_IS_REMOTE"
						status=$?
						[ "$status" -ne 0 ] && _gw6_failed="$OMR_TRACKER_DEVICE_GATEWAY6"
					#fi
					if [ "$status" -eq 0 ] && [ "$OMR_TRACKER_TYPE" = "none" ]; then
						OMR_TRACKER_STATUS_MSG=""
						OMR_TRACKER_STATUS="OK"
						break
					elif [ "$OMR_TRACKER_TYPE" != "none" ]; then
						if [ "$status" -ne 0 ]; then
							OMR_TRACKER_STATUS_MSG="IPv6 gateway down"
						fi
						serverip_ping=false
						if [ "$OMR_TRACKER_SERVER_DEFINED" = true ]; then
							if [ "$OMR_TRACKER_TYPE" = "ping" ]; then
								config_load openmptcprouter
								config_foreach _ping_server server $OMR_TRACKER_DEVICE
							fi
							if [ "$OMR_TRACKER_TYPE" = "httping" ]; then
								config_load openmptcprouter
								config_foreach _httping_server server $OMR_TRACKER_DEVICE_IP6
							fi
						fi

						if [ "$serverip_ping" = false ] && [ -n "$OMR_TRACKER_HOST6" ]; then
							_resolve_host6
							if [ -n "$OMR_TRACKER_HOST6" ]; then
								_probe_failed=""
								if [ -n "$_probe_route" ]; then
									# Check if route is not used
									_probe_tries=0
									while _is_ip "$OMR_TRACKER_HOST6" && ! ip -6 route add $OMR_TRACKER_HOST6 via $OMR_TRACKER_DEVICE_GATEWAY6 dev $OMR_TRACKER_DEVICE src $OMR_TRACKER_DEVICE_IP6 $OMR_TRACKER_PROBE_ROUTE > /dev/null 2>&1
									do
										# The address and the gateway are only looked up again
										# on the next cycle: when the modem got new ones the add
										# could never work, and the tracker waited here forever
										_probe_tries=$((_probe_tries + 1))
										if [ "$_probe_tries" -ge 3 ]; then
											_probe_failed=1
											break
										fi
										logger -t "omr-tracker" "Can't create route to $OMR_TRACKER_HOST6 via $OMR_TRACKER_DEVICE_GATEWAY6 dev $OMR_TRACKER_DEVICE src $OMR_TRACKER_DEVICE_IP6. waiting..."
										sleep 2
										ip -6 route del "$OMR_TRACKER_HOST6" via "$OMR_TRACKER_DEVICE_GATEWAY6" dev "$OMR_TRACKER_DEVICE" $OMR_TRACKER_PROBE_ROUTE > /dev/null 2>&1
										_restart
										_resolve_host6
									done
								fi
								if [ -n "$_probe_failed" ]; then
									statusb=1
								elif [ "$OMR_TRACKER_TYPE" = "ping" ]; then
									_ping "$OMR_TRACKER_HOST6" "$OMR_TRACKER_DEVICE" "$host_localip"
									statusb=$?
								elif [ "$OMR_TRACKER_TYPE" = "httping" ]; then
									_httping "$OMR_TRACKER_HOST6" "$OMR_TRACKER_DEVICE_IP6" "$host_localip"
									statusb=$?
								elif [ "$OMR_TRACKER_TYPE" = "dns" ]; then
									_dns "$OMR_TRACKER_HOST6" "$OMR_TRACKER_DEVICE_IP6" "$host_localip"
									statusb=$?
								fi
								[ -n "$_probe_route" ] && ip -6 route del "$OMR_TRACKER_HOST6" via "$OMR_TRACKER_DEVICE_GATEWAY6" dev "$OMR_TRACKER_DEVICE" $OMR_TRACKER_PROBE_ROUTE > /dev/null 2>&1
								if [ "$statusb" -eq 0 ]; then
									OMR_TRACKER_STATUS_MSG=""
									OMR_TRACKER_STATUS="OK"
								else
									if [ "$OMR_TRACKER_LIST_HOSTS6" = "" ]; then
										OMR_TRACKER_LIST_HOSTS6="$OMR_TRACKER_HOST6"
									else
										OMR_TRACKER_LIST_HOSTS6="$OMR_TRACKER_LIST_HOSTS6,$OMR_TRACKER_HOST6"
									fi
									OMR_TRACKER_STATUS_MSG="check error"
								fi
							fi
						else
							OMR_TRACKER_STATUS_MSG=""
							OMR_TRACKER_STATUS="OK"
						fi
						if [ "$serverip_ping" != false ] && [ "$OMR_TRACKER_STATUS" = "OK" ] && [ "$OMR_TRACKER_TYPE" != "httping" ] && [ "$OMR_TRACKER_SERVER_HTTP_TEST" = "1" ]; then
							serverip_ping=false
							config_load openmptcprouter
							config_foreach _httping_server server $OMR_TRACKER_DEVICE_IP6
							if [ "$serverip_ping" = false ]; then
								OMR_TRACKER_STATUS_MSG="No access to server API"
								#OMR_TRACKER_STATUS="ERROR"
							else
								OMR_TRACKER_STATUS_MSG=""
								OMR_TRACKER_STATUS="OK"
								break
							fi
						elif [ "$serverip_ping" = false ] && [ "$OMR_TRACKER_STATUS" = "OK" ] && [ "$OMR_TRACKER_SERVER_TEST" = "1" ]; then
							if [ "$OMR_TRACKER_TYPE" = "ping" ]; then
								_ping_server_all
							fi
							if [ "$OMR_TRACKER_TYPE" = "httping" ]; then
								_httping_server_all
							fi
							if [ "$serverip_ping" != false ] && [ "$servers" = true ]; then
								OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_STATUS_MSG No answer from server"
								OMR_TRACKER_STATUS="ERROR"
								#break
							fi
						fi
						[ "$OMR_TRACKER_STATUS" = "OK" ] && break
					elif [ "$status" -ne 0 ]; then
						OMR_TRACKER_STATUS_MSG="IPv6 gateway down"
					fi
					tries=$((tries - 1))
					#_restart
					OMR_TRACKER_HOST6="${OMR_TRACKER_HOSTS6%% *}"
					[ "$OMR_TRACKER_HOST6" = "$OMR_TRACKER_HOSTS6" ] || {
						OMR_TRACKER_HOSTS6="${OMR_TRACKER_HOSTS6#* } $OMR_TRACKER_HOST6"
					}
					#OMR_TRACKER_TIMEOUT=$((OMR_TRACKER_TIMEOUT * 2))
					sleep "${OMR_TRACKER_INTERVAL_TRIES:-1}"
				done
			fi
			if [ "$OMR_TRACKER_STATUS" = "ERROR" ] && { [ -z "$OMR_TRACKER_DEVICE_IP" ] || [ -z "$OMR_TRACKER_DEVICE_GATEWAY" ] || [ -z "$OMR_TRACKER_DEVICE_IP6" ] || [ -z "$OMR_TRACKER_DEVICE_GATEWAY6" ]; }; then
				[ -z "$OMR_TRACKER_STATUS_MSG" ] && OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_INTERFACE may have ip issues"
				[ -z "$OMR_TRACKER_DEVICE_IP" ] && OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_STATUS_MSG, interface have no IPv4"
				[ -z "$OMR_TRACKER_DEVICE_GATEWAY" ] && OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_STATUS_MSG, interface have no IPv4 gateway"
				[ "$OMR_TRACKER_IPV6" != "0" ] && [ -z "$OMR_TRACKER_DEVICE_IP6" ] && OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_STATUS_MSG, interface have no IPv6"
				[ "$OMR_TRACKER_IPV6" != "0" ] && [ -z "$OMR_TRACKER_DEVICE_GATEWAY6" ] && OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_STATUS_MSG, interface have no IPv6 gateway"
			elif [ "$OMR_TRACKER_STATUS" = "ERROR" ] && [ -z "$OMR_TRACKER_STATUS_MSG" ]; then
				OMR_TRACKER_STATUS_MSG="No IP & Gateway detected for $OMR_TRACKER_INTERFACE"
			fi
		else
			OMR_TRACKER_STATUS_MSG="link down"
		fi
	fi

	if [ "$OMR_TRACKER_LIST_HOSTS" != "" ]; then
		if [ "$OMR_TRACKER_STATUS_MSG" = "" ]; then
			OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_TYPE from $OMR_TRACKER_DEVICE_IP error ($OMR_TRACKER_LIST_HOSTS)"
		else
			OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_STATUS_MSG and $OMR_TRACKER_TYPE from $OMR_TRACKER_DEVICE_IP error ($OMR_TRACKER_LIST_HOSTS)"
		fi
	fi
	if [ "$OMR_TRACKER_LIST_HOSTS6" != "" ]; then
		if [ "$OMR_TRACKER_STATUS_MSG" = "" ]; then
			OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_TYPE from $OMR_TRACKER_DEVICE_IP6 error ($OMR_TRACKER_LIST_HOSTS6)"
		else
			OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_STATUS_MSG and $OMR_TRACKER_TYPE from $OMR_TRACKER_DEVICE_IP6 error ($OMR_TRACKER_LIST_HOSTS6)"
		fi
	fi
	if [ "$OMR_TRACKER_CHECK_QUALITY" = "1" ]; then
		_read_congestion
		if [ "$OMR_TRACKER_PREV_STATUS" = "OK" ] && [ "$OMR_TRACKER_STATUS" = "OK" ]; then
			if [ -n "$OMR_TRACKER_LOSS" ] && [ -n "$OMR_TRACKER_FAILURE_LOSS" ] && [ "$OMR_TRACKER_LOSS" -ge "$OMR_TRACKER_FAILURE_LOSS" ]; then
				OMR_TRACKER_STATUS="ERROR"
				OMR_TRACKER_STATUS_MSG="Packet loss is $OMR_TRACKER_LOSS this is more than failure limit defined at $OMR_TRACKER_FAILURE_LOSS"
			elif [ -n "$OMR_TRACKER_LATENCY" ] && [ -n "$OMR_TRACKER_FAILURE_LATENCY" ] && [ "$OMR_TRACKER_LATENCY" -ge "$OMR_TRACKER_FAILURE_LATENCY" ]; then
				OMR_TRACKER_STATUS="ERROR"
				OMR_TRACKER_STATUS_MSG="Latency is $OMR_TRACKER_LATENCY this is more than failure limit defined at $OMR_TRACKER_FAILURE_LATENCY"
			elif [ -n "$OMR_TRACKER_CONGESTION_SCORE" ] && [ -n "$OMR_TRACKER_FAILURE_CONGESTION" ] && [ "$OMR_TRACKER_CONGESTION_SCORE" -ge "$OMR_TRACKER_FAILURE_CONGESTION" ] 2>/dev/null; then
				OMR_TRACKER_STATUS="ERROR"
				OMR_TRACKER_STATUS_MSG="Congestion score is $OMR_TRACKER_CONGESTION_SCORE this is more than failure limit defined at $OMR_TRACKER_FAILURE_CONGESTION"
			fi
		elif { [ "$OMR_TRACKER_PREV_STATUS" = "ERROR" ] || [ -n "$_started_down" ]; } && [ "$OMR_TRACKER_STATUS" = "OK" ]; then
			if [ -n "$OMR_TRACKER_LOSS" ] && [ -n "$OMR_TRACKER_RECOVERY_LOSS" ] && [ "$OMR_TRACKER_LOSS" -ge "$OMR_TRACKER_RECOVERY_LOSS" ]; then
				OMR_TRACKER_STATUS="ERROR"
				OMR_TRACKER_STATUS_MSG="Packet loss is $OMR_TRACKER_LOSS this is more than recovery limit defined at $OMR_TRACKER_RECOVERY_LOSS"
			elif [ -n "$OMR_TRACKER_LATENCY" ] && [ -n "$OMR_TRACKER_RECOVERY_LATENCY" ] && [ "$OMR_TRACKER_LATENCY" -ge "$OMR_TRACKER_RECOVERY_LATENCY" ]; then
				OMR_TRACKER_STATUS="ERROR"
				OMR_TRACKER_STATUS_MSG="Latency is $OMR_TRACKER_LATENCY this is more than recovery limit defined at $OMR_TRACKER_RECOVERY_LATENCY"
			elif [ -n "$OMR_TRACKER_CONGESTION_SCORE" ] && [ -n "$OMR_TRACKER_RECOVERY_CONGESTION" ] && [ "$OMR_TRACKER_CONGESTION_SCORE" -ge "$OMR_TRACKER_RECOVERY_CONGESTION" ] 2>/dev/null; then
				OMR_TRACKER_STATUS="ERROR"
				OMR_TRACKER_STATUS_MSG="Congestion score is $OMR_TRACKER_CONGESTION_SCORE this is more than recovery limit defined at $OMR_TRACKER_RECOVERY_CONGESTION"
			fi
		fi
	fi

	# tries_up ("Interface up" in the UI) is documented as "Downed interface
	# will be deemed up after this many successful ping tests", i.e. N
	# consecutive good cycles, not just one. Without this gate the block
	# above flips OMR_TRACKER_STATUS to OK on the very first reachable cycle
	# after an ERROR, which fires the post-tracking recovery cascade (default
	# route restore, MPTCP master promotion, weight restore, ...) long before
	# the configured up counter is satisfied and while the link may still be
	# flaky right after recovery (issue #4355).
	# OMR_TRACKER_RECOVERING tells the hooks that this ERROR is only the
	# streak's: the probe answered, so 002-error must not reset a modem that
	# works again.
	OMR_TRACKER_RECOVERING=""
	if [ "$OMR_TRACKER_PREV_STATUS" = "ERROR" ] || [ -n "$_started_down" ]; then
		if [ "$OMR_TRACKER_STATUS" = "OK" ]; then
			OMR_TRACKER_UP_STREAK=$((OMR_TRACKER_UP_STREAK + 1))
			if [ "$OMR_TRACKER_UP_STREAK" -lt "${OMR_TRACKER_TRIES_UP:-1}" ]; then
				logger -t "omr-tracker" "$OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE) reachable again, waiting for stable recovery ($OMR_TRACKER_UP_STREAK/${OMR_TRACKER_TRIES_UP:-1})"
				OMR_TRACKER_STATUS="ERROR"
				# The probe answered, so no failure message is set: after a
				# restart while down, omr-events logged this as "down: unknown"
				[ -z "$OMR_TRACKER_STATUS_MSG" ] && OMR_TRACKER_STATUS_MSG="waiting for stable recovery"
				OMR_TRACKER_RECOVERING=1
			fi
		else
			OMR_TRACKER_UP_STREAK=0
		fi
	else
		OMR_TRACKER_UP_STREAK=0
	fi
	# Started while the WAN was down: it already went through the down
	# transition. A cycle the streak holds, or the recovery ending it, is
	# shown to the hooks after an ERROR, as for a tracker that saw the
	# failure: with no previous status, 002-error ran the whole transition
	# on each held start (down mail, VPN restart, restart_down's ifdown and
	# ifup, whose hotplug restarted this tracker, held again). A failure has
	# no previous status, so the hooks apply it again: an ifup meanwhile
	# can have put the WAN's routes and endpoints back.
	if [ -n "$_started_down" ]; then
		if [ "$OMR_TRACKER_STATUS" = "OK" ] || [ -n "$OMR_TRACKER_RECOVERING" ]; then
			OMR_TRACKER_PREV_STATUS="ERROR"
		else
			OMR_TRACKER_PREV_STATUS=""
		fi
	fi

	#[ "$OMR_TRACKER_HOSTS" = "$initial_hosts" ] || [ "$OMR_TRACKER_STATUS" = "OK" ] && _post_tracking
	#[ "$OMR_TRACKER_STATUS" = "ERROR" ] && _restart
	#[ "$OMR_TRACKER_STATUS" != "$OMR_TRACKER_PREV_STATUS" ] && _post_tracking
	# The post-tracking cascade sources every script in post-tracking.d (dozens
	# of forks per run): run it immediately on any state transition so failover
	# is not delayed. The periodic refresh work (status, latencies, weights,
	# metrics) is not time-critical, so it runs at the normal
	# OMR_TRACKER_POST_INTERVAL cadence while the link is OK, but backs off to
	# the much longer OMR_TRACKER_POST_INTERVAL_DOWN while it stays down: a
	# dead link has nothing new to report between transitions, so there's no
	# reason to keep paying the full post-tracking.d fork cost every cycle
	# for every down interface.
	printf -v _post_tracking_now '%(%s)T' -1
	if [ "$OMR_TRACKER_STATUS" = "OK" ]; then
		_post_tracking_due="${OMR_TRACKER_POST_INTERVAL:-10}"
	else
		_post_tracking_due="${OMR_TRACKER_POST_INTERVAL_DOWN:-${OMR_TRACKER_POST_INTERVAL:-10}}"
	fi
	if [ "$OMR_TRACKER_STATUS" != "$OMR_TRACKER_PREV_STATUS" ] \
		|| [ "$OMR_TRACKER_DEVICE_IP" != "$OMR_TRACKER_PREV_DEVICE_IP" ] \
		|| [ "$OMR_TRACKER_DEVICE_IP6" != "$OMR_TRACKER_PREV_DEVICE_IP6" ] \
		|| [ $((_post_tracking_now - _post_tracking_last)) -ge "$_post_tracking_due" ]; then
		_post_tracking
		_post_tracking_last=$_post_tracking_now
	fi
	[ "$OMR_TRACKER_STATUS" = "ERROR" ] && sleep $wait_test
	OMR_TRACKER_PREV_STATUS="$OMR_TRACKER_STATUS"
	# Until a cycle the streak doesn't hold
	[ -n "$OMR_TRACKER_RECOVERING" ] || _started_down=""
	OMR_TRACKER_PREV_DEVICE_IP="$OMR_TRACKER_DEVICE_IP"
	OMR_TRACKER_PREV_DEVICE_IP6="$OMR_TRACKER_DEVICE_IP6"
	OMR_TRACKER_PREV_GATEWAY="$OMR_TRACKER_DEVICE_GATEWAY"
	OMR_TRACKER_PREV_GATEWAY6="$OMR_TRACKER_DEVICE_GATEWAY6"
	_restart

	if [ "$OMR_TRACKER_STATUS" = "ERROR" ]; then
		if [ -z "$OMR_TRACKER_EVER_OK" ] && [ -z "$OMR_TRACKER_PINGED" ]; then
			# The interface has never validated and isn't even up yet (no ping
			# was possible, so the cycle was nearly free): boot/bring-up phase —
			# recheck at the normal interval so the first route isn't delayed
			# by the failure backoff.
			sleep "${OMR_TRACKER_INTERVAL:-2}"
		else
			[ -n "$OMR_TRACKER_FAILURE_INTERVAL" ] && _failure_sleep "$OMR_TRACKER_FAILURE_INTERVAL"
		fi
	else
		OMR_TRACKER_EVER_OK=1
		sleep "${OMR_TRACKER_INTERVAL:-2}"
	fi
done
