#!/bin/sh
#
# Copyright (C) 2018-2025 Ycarus (Yannick Chabanois) <ycarus@zugaina.org> for OpenMPTCProuter
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
# This script do commands when an interface is detected as down


# ubus directly, like the other hooks: /sbin/ifstatus is a shell wrapper
# around the same call that costs two more processes, on every run
interface_up=$(ubus call network.interface."$OMR_TRACKER_INTERFACE" status 2>/dev/null | jsonfilter -q -e '@["up"]')

. "${OMR_LIB_DIR:-/usr/share/omr/lib}/common-post-tracking.sh"

# A quota cut is an administrative decision, not a connectivity failure.
# In particular, ModemManager reports the modem as disabled after omr-quota
# calls ifdown; the recovery code below would otherwise immediately enable it
# and call ifup again.  Marker contents cover global quotas, while the basename
# check also supports per-interface/legacy empty markers.
_omr_quota_cut_active() {
	local interface="$1" marker quota_interface
	[ -n "$interface" ] || return 1
	for marker in "${OMR_QUOTA_RUNTIME_DIR:-/tmp/omr-quota}"/*.cut; do
		[ -f "$marker" ] || continue
		[ "${marker##*/}" = "${interface}.cut" ] && return 0
		for quota_interface in $(cat "$marker" 2>/dev/null); do
			[ "$quota_interface" = "$interface" ] && return 0
		done
	done
	return 1
}

quota_cut="false"
if _omr_quota_cut_active "$OMR_TRACKER_INTERFACE"; then
	quota_cut="true"
fi

# The down mail and script alerts, also sent in monitor only mode
_alert_down() {
	mail_alert=""
	if [ -n "$OMR_TRACKER_INTERFACE" ]; then
		mail_alert="$(uci -q get omr-tracker.$OMR_TRACKER_INTERFACE.mail_alert)"
	fi
	[ -z "$mail_alert" ] && mail_alert="$(uci -q get omr-tracker.defaults.mail_alert)"
	mail_to="$(uci -q get mail.default.to | tr -d '\r\n')"
	case "$mail_to" in
		-*|*[[:space:]]*) mail_to="" ;;
	esac
	[ "$mail_alert" = "1" ] && [ -n "$mail_to" ] && {
		OMR_SYSNAME="$(uci -q get system.@system[0].hostname)"
		if [ "$(uci -q get omr-tracker.defaults.mail_down_subject)" != "" ] && [ "$(uci -q get omr-tracker.defaults.mail_down_message)" != "" ]; then
			mail_subject="$(uci -q get omr-tracker.defaults.mail_down_subject)"
			mail_subject=`echo $mail_subject | sed -e "s/%SYSNAME%/$OMR_SYSNAME/g" -e "s/%INTERFACE%/$OMR_TRACKER_INTERFACE/g" -e "s/%DEVICE%/$OMR_TRACKER_DEVICE/g" -e "s/%MESSAGE%/$OMR_TRACKER_STATUS_MSG/g"`
			mail_subject="$(printf '%s' "$mail_subject" | tr -d '\r\n')"
			mail_message="$(uci -q get omr-tracker.defaults.mail_down_message)"
			mail_message=`echo $mail_message | sed -e "s/%SYSNAME%/$OMR_SYSNAME/g" -e "s/%INTERFACE%/$OMR_TRACKER_INTERFACE/g" -e "s/%DEVICE%/$OMR_TRACKER_DEVICE/g" -e "s/%MESSAGE%/$OMR_TRACKER_STATUS_MSG/g"`
			printf 'Subject: %s\n\n%s\n' "$mail_subject" "$mail_message" | sendmail "$mail_to"
		else
			mail_subject="$(printf '%s' "$OMR_SYSNAME: $OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE) is down" | tr -d '\r\n')"
			printf 'Subject: %s\n\nConnection failure of %s (%s) detected. The reason is "%s".\n' "$mail_subject" "$OMR_TRACKER_INTERFACE" "$OMR_TRACKER_DEVICE" "$OMR_TRACKER_STATUS_MSG" | sendmail "$mail_to"
		fi
	}
	script_alert_down=""
	if [ -n "$OMR_TRACKER_INTERFACE" ]; then
		script_alert_down="$(uci -q get omr-tracker.$OMR_TRACKER_INTERFACE.script_alert_down)"
	fi
	[ -n "$script_alert_down" ] && eval $script_alert_down
}

# Monitor only: the interface state is reported (log, mail and script
# alerts, status page) and nothing is changed. The failure doesn't go in
# "state", which mptcp, omr-bypass, omr-quota and keepalived-ha act on,
# but in "monitor_state", only read by the status page.
if [ "$OMR_TRACKER_MONITOR_ONLY" = "1" ]; then
	if [ "$OMR_TRACKER_STATUS" = "ERROR" ]; then
		[ "$(omr_state_get "openmptcprouter.$OMR_TRACKER_INTERFACE.monitor_state")" = "down" ] || \
			omr_state_set openmptcprouter "$OMR_TRACKER_INTERFACE" monitor_state 'down'
		if [ "$OMR_TRACKER_PREV_STATUS" != "ERROR" ]; then
			_log "$OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE) is down${OMR_TRACKER_STATUS_MSG:+ because $OMR_TRACKER_STATUS_MSG}, nothing changed (monitor only)"
			_alert_down
		fi
		# Monitor only turned on while the interface was down in normal
		# mode: it stayed marked down, without MPTCP, out of the default
		# routes and of the VPN paths, while the help says it stays in use.
		# 003-up only puts it back on an OK, so run once what a recovery
		# does. Not while netifd has it down: there is nothing to use yet,
		# the next cycle checks again.
		if [ "$interface_up" = "true" ] && [ "$(omr_state_get "openmptcprouter.$OMR_TRACKER_INTERFACE.state")" = "down" ]; then
			_log "$OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE) was set down before monitor only, put it back in use"
			# No previous status: a recovery without the up mail and alert,
			# the interface still fails its checks. 025 (omr-bypass, another
			# package) puts the interface back in the output WAN lists and
			# source policies, which kept avoiding it on the ERROR it saw.
			for _omr_hook in 003-up 004-glorytun-udp-path 005-mqvpn-path 025-omr-bypass-failover; do
				[ -x "${OMR_LIB_DIR:-/usr/share/omr/lib}/../post-tracking.d/$_omr_hook" ] || continue
				(
					OMR_TRACKER_STATUS="OK"
					OMR_TRACKER_PREV_STATUS=""
					# shellcheck source=/dev/null
					. "${OMR_LIB_DIR:-/usr/share/omr/lib}/../post-tracking.d/$_omr_hook"
				)
			done
			env -i ACTION="connecting" INTERFACE="$OMR_TRACKER_INTERFACE" DEVICE="$OMR_TRACKER_DEVICE" /sbin/hotplug-call iface >/dev/null 2>&1
		fi
	elif [ "$OMR_TRACKER_PREV_STATUS" != "OK" ]; then
		omr_state_del openmptcprouter "$OMR_TRACKER_INTERFACE" monitor_state
	fi
	exit 0
fi

if [ "$OMR_TRACKER_STATUS" = "ERROR" ] && [ "$(omr_state_get openmptcprouter.$OMR_TRACKER_INTERFACE.state)" != "down" ]; then
	omr_state_set openmptcprouter $OMR_TRACKER_INTERFACE state 'down'
fi

# An interface in error will never be used in MPTCP
if [ "$OMR_TRACKER_STATUS" = "ERROR" ] || { [ "$OMR_TRACKER_INTERFACE" != "omrvpn" ] && [ "$interface_up" != "true" ]; }; then
	#interface_autostart=$(ifstatus "$OMR_TRACKER_INTERFACE" 2>/dev/null | jsonfilter -q -e '@["autostart"]')
	#interface_available=$(ifstatus "$OMR_TRACKER_INTERFACE" 2>/dev/null | jsonfilter -q -e '@["available"]')
	#interface_pending=$(ifstatus "$OMR_TRACKER_INTERFACE" 2>/dev/null | jsonfilter -q -e '@["pending"]')
	#interface_error=$(ifstatus "$OMR_TRACKER_INTERFACE" 2>/dev/null | jsonfilter -q -e '@["errors"][0].code')

	#if [ "$interface_available" = "true" ] && [ -n "$OMR_TRACKER_INTERFACE" ] && ([ "$(uci -q get network.$OMR_TRACKER_INTERFACE.proto)" = "modemmanager" ] || [ "$(uci -q get network.$OMR_TRACKER_INTERFACE.proto)" = "wireguard" ]) && [ "$interface_error" != "MM_PINCODE_REQUIRED" ] && [ "$interface_error" != "MM_FAILED_REASON_SIM_MISSING" ]; then
	#if [ "$interface_available" = "true" ] && [ "$interface_up" = "false" ] && [ -n "$OMR_TRACKER_INTERFACE" ] && ([ "$(uci -q get network.$OMR_TRACKER_INTERFACE.proto)" = "modemmanager" ] || [ "$(uci -q get network.$OMR_TRACKER_INTERFACE.proto)" = "wireguard" ]); then
	#	_log "No answer from $OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE), restart interface"
	#	_log "Set $OMR_TRACKER_INTERFACE down"
	#	ifdown $OMR_TRACKER_INTERFACE
	#	sleep 5
	#	_log "Set $OMR_TRACKER_INTERFACE up"
	#	ifup $OMR_TRACKER_INTERFACE
	#	sleep 30
	#if [ -n "$OMR_TRACKER_INTERFACE" ] && [ -n "$OMR_TRACKER_DEVICE" ] && [ "$(uci -q get network.$OMR_TRACKER_INTERFACE.proto)" = "modemmanager" ] && [ "$interface_up" = "false" ] && [ "$interface_available" = "false" ] && [ "$interface_pending" = "false" ] && [ "$interface_autostart" = "true" ]; then
	# Not while the tries_up streak holds the status in ERROR: the modem
	# answers again, resetting it there restarted the whole recovery
	if [ "$quota_cut" != "true" ] && [ -z "$OMR_TRACKER_RECOVERING" ] && [ -n "$OMR_TRACKER_INTERFACE" ] && [ "$(uci -q get network.$OMR_TRACKER_INTERFACE.proto)" = "modemmanager" ] && [ "$(uci -q get openmptcprouter.settings.disable_modemmanager)" != "1" ]; then
		modemfind="0"
		device="$(uci -q get network.$OMR_TRACKER_INTERFACE.device)"
		. /usr/share/ModemManager/modemmanager.common
		[ -n "$device" ] && MODEM_STATUS="$(timeout 3 mmcli -m $device -K 2>/dev/null)"
		modem=$device
		if [ -n "$MODEM_STATUS" ]; then
			modemfind="1"
			mm_state=$(modemmanager_get_field "${MODEM_STATUS}" "modem.generic.state")
			if [ "$mm_state" = "failed" ]; then
				mm_state_failed=$(modemmanager_get_field "${MODEM_STATUS}" "modem.generic.state-failed-reason")
				if [ "$mm_state_failed" = "unknown-capabilities" ]; then
					_log "Interface $OMR_TRACKER_INTERFACE in failed state in ModemManager, reset modem..."
					/usr/bin/mmcli -m ${modem} -r >/dev/null 2>&1
					sleep $(uci -q get network.$OMR_TRACKER_INTERFACE.delay || echo '120')
				elif false && [ "$mm_state_failed" = "sim-missing" ]; then
					_log "Interface $OMR_TRACKER_INTERFACE in failed state in ModemManager, reset modem..."
					/usr/bin/mmcli -m ${modem} -r >/dev/null 2>&1
					#_log "Interface $OMR_TRACKER_INTERFACE in failed state sim-missing in ModemManager, reset modem..."
					#mm_primary_port=$(modemmanager_get_field "${MODEM_STATUS}" "modem.generic.primary-port")
					#mbimcli -p -d /dev/$mm_primary_port --ms-device-reset >/dev/null 2>&1
				fi
			elif [ "$mm_state" = "enabled" ] || [ "$mm_state" = "connected" ]; then
				restart_intf_count="$(omr_state_get openmptcprouter.$OMR_TRACKER_INTERFACE.restart_intf)"
				[ -z "$restart_intf_count" ] && restart_intf_count=0
				restart_intf_count=$((restart_intf_count + 1))

				switch_operator="$(uci -q get omr-tracker.$OMR_TRACKER_INTERFACE.switch_operator_on_no_traffic)"
				[ -z "$switch_operator" ] && switch_operator="$(uci -q get omr-tracker.defaults.switch_operator_on_no_traffic)"
				[ -z "$switch_operator" ] && switch_operator="1"

				switch_operator_tries="$(uci -q get omr-tracker.$OMR_TRACKER_INTERFACE.switch_operator_tries)"
				[ -z "$switch_operator_tries" ] && switch_operator_tries="$(uci -q get omr-tracker.defaults.switch_operator_tries)"
				[ -z "$switch_operator_tries" ] && switch_operator_tries=3

				switch_operator_plmn="$(uci -q get omr-tracker.$OMR_TRACKER_INTERFACE.switch_operator_plmn)"
				[ -z "$switch_operator_plmn" ] && switch_operator_plmn="$(uci -q get omr-tracker.defaults.switch_operator_plmn)"

				no_traffic_error=0
				case "${OMR_TRACKER_STATUS_MSG:-}" in
					*"No answer from server"*|*"No access to server API"*|*"check error"*)
						no_traffic_error=1
					;;
				esac

				if [ "$switch_operator" = "1" ] && [ "$OMR_TRACKER_STATUS" = "ERROR" ] && [ "$interface_up" = "true" ] && [ "$no_traffic_error" = "1" ] && [ "$restart_intf_count" -ge "$switch_operator_tries" ]; then
					mm_operator_code=$(modemmanager_get_field "${MODEM_STATUS}" "modem.3gpp.operator-code")
					if [ -n "$switch_operator_plmn" ] && [ "$switch_operator_plmn" != "$mm_operator_code" ]; then
						_log "No traffic on $OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE), switch operator to $switch_operator_plmn"
						/usr/bin/mmcli -m ${modem} --command='AT+COPS=2' >/dev/null 2>&1
						sleep 4
						/usr/bin/mmcli -m ${modem} --command="AT+COPS=1,2,\"$switch_operator_plmn\"" >/dev/null 2>&1
					else
						_log "No traffic on $OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE), trigger automatic operator reselection"
						/usr/bin/mmcli -m ${modem} --command='AT+COPS=2' >/dev/null 2>&1
						sleep 4
						/usr/bin/mmcli -m ${modem} --command='AT+COPS=0' >/dev/null 2>&1
					fi
					_log "Set $OMR_TRACKER_INTERFACE down"
					ifdown $OMR_TRACKER_INTERFACE >/dev/null 2>&1
					sleep 5
					_log "Set $OMR_TRACKER_INTERFACE up"
					ifup $OMR_TRACKER_INTERFACE >/dev/null 2>&1
					sleep $(uci -q get network.$OMR_TRACKER_INTERFACE.delay || echo '120')
					restart_intf_count=0
				else
					_log "No answer from $OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE), restart interface"
					_log "Set $OMR_TRACKER_INTERFACE down"
					ifdown $OMR_TRACKER_INTERFACE >/dev/null 2>&1
					sleep 5
					_log "Set $OMR_TRACKER_INTERFACE up"
					ifup $OMR_TRACKER_INTERFACE >/dev/null 2>&1
					sleep $(uci -q get network.$OMR_TRACKER_INTERFACE.delay || echo '120')
				fi

				omr_state_set openmptcprouter $OMR_TRACKER_INTERFACE restart_intf $restart_intf_count
			elif [ "$mm_state" = "disabled" ] && [ "$(uci -q get network.$OMR_TRACKER_INTERFACE.force_link)" = "1" ]; then
			# [ -n "$(echo $modeminfo | grep 'modem.3gpp.registration-state ' | grep home)" ]; then
				_log "Interface $OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE) disabled, set it up"
				/usr/bin/mmcli -m ${modem} -e >/dev/null 2>&1
				ifup $OMR_TRACKER_INTERFACE >/dev/null 2>&1
				sleep $(uci -q get network.$OMR_TRACKER_INTERFACE.delay || echo '120')
			elif [ "$mm_state" = "registered" ]; then
				_log "Interface $OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE) registered, reset it"
				ifup $OMR_TRACKER_INTERFACE >/dev/null 2>&1
				#/usr/bin/mmcli -m ${modem} -r >/dev/null 2>&1
				sleep $(uci -q get network.$OMR_TRACKER_INTERFACE.delay || echo '120')
			elif [ "$mm_state" = "locked" ]; then
				mm_unlock_required=$(modemmanager_get_field "${MODEM_STATUS}" "modem.generic.unlock-required")
				mm_generic_sim=$(modemmanager_get_field "${MODEM_STATUS}" "modem.generic.sim")
				#mm_primary_port=$(modemmanager_get_field "${MODEM_STATUS}" "modem.generic.primary-port")
				#mm_drivers=$(modemmanager_get_multivalue_field "${MODEM_STATUS}" "modem.generic.drivers")
				mm_unlock_retries=$(modemmanager_get_multivalue_field "${MODEM_STATUS}" "modem.generic.unlock-retries" | awk '{print substr($2,2,length($2)-2)}')
				#'
				if [ "$mm_unlock_required" = "sim-pin" ]; then
					# Sometimes PIN is not correctly sent to modem
					if [ -n "$(uci -q get network.$OMR_TRACKER_INTERFACE.pincode)" ]; then
						if [ "$mm_unlock_retries" -gt "1" ] && [ -n "$(uci -q get network.$OMR_TRACKER_INTERFACE.pincode)" ]; then
							_log "Interface $OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE) locked, set PIN"
							mmcli -a -m ${modem} -i $mm_generic_sim --pin=$(uci -q get network.$OMR_TRACKER_INTERFACE.pincode) >/dev/null 2>&1
							sleep 30
							ifup $OMR_TRACKER_INTERFACE >/dev/null 2>&1
						fi
					fi
				elif [ "$mm_unlock_required" = "sim-puk" ] && [ "$mm_unlock_retries" -gt "1" ] && [ -n "$(uci -q get network.$OMR_TRACKER_INTERFACE.pukcode)" ] && [ -n "$(uci -q get network.$OMR_TRACKER_INTERFACE.pincode)" ]; then
					_log "Interface $OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE) locked, set PUK"
					mmcli -i $mm_generic_sim --puk=$(uci -q get network.$OMR_TRACKER_INTERFACE.pukcode) --pin=$(uci -q get network.$OMR_TRACKER_INTERFACE.pincode) >/dev/null 2>&1
				fi
				sleep $(uci -q get network.$OMR_TRACKER_INTERFACE.delay || echo '120')
			fi
		fi
		if [ "$modemfind" = "0" ]; then
			#_log "Can't find $OMR_TRACKER_INTERFACE in ModemManager, rescan modem..."
			#/usr/bin/mmcli -S >/dev/null 2>&1
			#_log "Can't find $OMR_TRACKER_INTERFACE in ModemManager, restart modemmanager..."
			_log "Can't find $OMR_TRACKER_INTERFACE in ModemManager"
			#/etc/init.d/modemmanager restart
			sleep $(uci -q get network.$OMR_TRACKER_INTERFACE.delay || echo '120')
		fi
	fi
	#elif [ -n "$OMR_TRACKER_INTERFACE" ] && [ -n "$OMR_TRACKER_DEVICE" ] && [ "$(uci -q get network.$OMR_TRACKER_INTERFACE.proto)" = "modemmanager" ] && [ "$interface_available" = "false" ]; then
	#	for modem in $(timeout 3 mmcli -L | awk -F/ '{ print $6}' | awk '{print $1}'); do
	#		if [ -n "$(timeout 2 mmcli -m $modem -K | grep $OMR_TRACKER_DEVICE)" ]; then
	#			modem_device=$(timeout 2 mmcli -m $modem -K | grep 'modem.generic.device ' | awk '{print $3}')
	#			[ -n "$modem_device" ] && modem_interface=$(uci -q show network | grep $modem_device | awk -F. '{print $2}')
	#			[ -n "$modem_interface" ] && {
	#				_log "No answer from $OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE), restart associed interface $modem_interface"
	#				ifdown $modem_interface
	#				sleep 5
	#				ifup $modem_interface
	#				sleep 30
	#			}
	#		fi
	#	done
	#fi

	# This part must be done after modems restart because we have no idea when modems will be ready again... (another solution would be to check ModemManager status)
	if [ "$OMR_TRACKER_PREV_STATUS" = "$OMR_TRACKER_STATUS" ]; then
		exit 0
	fi
	. /lib/functions/network.sh

	[ -z "$OMR_TRACKER_DEVICE" ] && OMR_TRACKER_DEVICE="$OMR_TRACKER_PREV_DEVICE"

	# Get the current multipath status
	multipath_status="off"
#	[ "$OMR_TRACKER_INTERFACE" = "omrvpn" ] && multipath $OMR_TRACKER_DEVICE off >/dev/null 2>&1
#	[ "$OMR_TRACKER_INTERFACE" = "omr6in4" ] && multipath $OMR_TRACKER_DEVICE off >/dev/null 2>&1
	if [ -e "${OMR_SYS_CLASS_NET_DIR:-/sys/class/net}/$OMR_TRACKER_DEVICE" ]; then
		case "$(multipath "$OMR_TRACKER_DEVICE" 2>/dev/null)" in
			*default*)      multipath_status="on"       ;;
			*backup*)       multipath_status="backup"   ;;
			*handover*)     multipath_status="handover" ;;
		esac
	fi

	if [ "$multipath_status" != "off" ] || { [ -n "$OMR_TRACKER_INTERFACE" ] && [ "$(uci -q get openmptcprouter.$OMR_TRACKER_INTERFACE.multipathvpn)" = "1" ]; }; then
		disable_multipath_off=""
		if [ -n "$OMR_TRACKER_INTERFACE" ]; then
			disable_multipath_off="$(uci -q get omr-tracker.$OMR_TRACKER_INTERFACE.disable_multipath_off)"
		fi
		[ -z "$disable_multipath_off" ] && disable_multipath_off="$(uci -q get omr-tracker.defaults.disable_multipath_off)"
		[ -z "$disable_multipath_off" ] && disable_multipath_off="0"

		if [ "$OMR_TRACKER_STATUS_MSG" = "" ]; then
			_log "$OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE) switched off (interface up status: $interface_up - Tracker status: $OMR_TRACKER_STATUS)"
		else
			_log "$OMR_TRACKER_INTERFACE ($OMR_TRACKER_DEVICE) switched off because $OMR_TRACKER_STATUS_MSG"
		fi
		if [ -n "$OMR_TRACKER_INTERFACE" ]; then
			omr_state_set openmptcprouter $OMR_TRACKER_INTERFACE state 'down'
			env -i ACTION="disconnecting" INTERFACE="$OMR_TRACKER_INTERFACE" DEVICE="$OMR_TRACKER_DEVICE" /sbin/hotplug-call iface
		fi
		# disable_multipath_off only keeps multipath on when the interface is
		# merely down; a tracker ERROR always turns multipath off
		if [ "$disable_multipath_off" != "1" ] || [ "$OMR_TRACKER_STATUS" = "ERROR" ]; then
			multipath "$OMR_TRACKER_DEVICE" off > /dev/null 2>&1
		fi
		# Not for the glorytun-udp tracking type ("glorytun" never matched
		# it): its status is the path's own, a path set down is dropped by
		# mud and the WAN could not be seen recovering anymore
		if [ "$(uci -q get glorytun-udp.vpn.enable)" = "1" ] && [ "$OMR_TRACKER_TYPE" != "glorytun-udp" ]; then
			gtudp_dev="$(uci -q get glorytun-udp.vpn.dev)"
			[ -z "$gtudp_dev" ] && gtudp_dev="tun0"
			# When the interface lost its IP (link down), the current IP is
			# empty: fall back to the last known IP so the path is really set
			# down and not left up in Glorytun UDP
			gtudp_down_ip="$OMR_TRACKER_DEVICE_IP"
			[ -z "$gtudp_down_ip" ] && gtudp_down_ip="$OMR_TRACKER_PREV_DEVICE_IP"
			gtudp_down_ip6="$OMR_TRACKER_DEVICE_IP6"
			[ -z "$gtudp_down_ip6" ] && gtudp_down_ip6="$OMR_TRACKER_PREV_DEVICE_IP6"
			if [ -n "$gtudp_down_ip" ]; then
				glorytun-udp path addr "$gtudp_down_ip" dev "$gtudp_dev" set down > /dev/null 2>&1
			fi
			if [ -n "$gtudp_down_ip6" ]; then
				glorytun-udp path addr "$gtudp_down_ip6" dev "$gtudp_dev" set down > /dev/null 2>&1
			fi
			# Set down any path whose local address is no longer assigned to
			# any interface (stale path from an old IP)
			for gtudp_path_addr in $(glorytun-udp path 2>/dev/null | awk '$1 == "path" {print $2}'); do
				[ -n "$(ip -o addr show 2>/dev/null | grep -F " ${gtudp_path_addr}/")" ] && continue
				_log "Set down stale Glorytun UDP path $gtudp_path_addr (address no longer assigned)"
				glorytun-udp path addr "$gtudp_path_addr" dev "$gtudp_dev" set down > /dev/null 2>&1
			done
		fi
		# Make traffic sourced from this interface fail fast instead of leaking
		# to another WAN via the main routing table when its own table has no
		# default route anymore: such leaked packets get NATed by the other WAN
		# and can keep dead VPN paths (Glorytun UDP) artificially alive
		intf_table="$(uci -q get network.$OMR_TRACKER_INTERFACE.ip4table)"
		[ -z "$intf_table" ] && intf_table="$(uci -q get network.$OMR_TRACKER_INTERFACE.metric)"
		intf_table6="$(uci -q get network.$OMR_TRACKER_INTERFACE.ip6table)"
		[ -z "$intf_table6" ] && [ -n "$intf_table" ] && intf_table6="6$intf_table"
		if [ -n "$intf_table" ]; then
			ip route replace unreachable default metric 999999 table $intf_table >/dev/null 2>&1
			ip -6 route replace unreachable default metric 999999 table $intf_table6 >/dev/null 2>&1
		fi
		if [ -n "$OMR_TRACKER_INTERFACE" ] && [ "$(uci -q get openmptcprouter.$OMR_TRACKER_INTERFACE.vpn)" = "1" ] && [ "$(uci -q get openmptcprouter.settings.mptcpovervpn)" = "openvpn" ]; then
			VPN_BASE_INTF="$(uci -q get openmptcprouter.$OMR_TRACKER_INTERFACE.baseintf)"
			if [ -n "$VPN_BASE_INTF" ]; then
				VPN_BASE_INTF_IP=$(ubus call network.interface.$VPN_BASE_INTF status | jsonfilter -e '@["ipv4-address"][0].address' | tr -d "\n")
				uci -q batch <<-EOF >/dev/null
					set openvpn.$VPN_BASE_INTF.local="$VPN_BASE_INTF_IP"
					commit openvpn
				EOF
			fi
			[ -z "$VPN_BASE_INTF" ] && VPN_BASE_INTF="omr"
			/etc/init.d/openvpn restart "$VPN_BASE_INTF" >/dev/null 2>&1
		fi
		# In balancing mode none of the branches below handle a non-master
		# interface failure: the balancing default route is rebuilt without
		# the down interface, else a dead nexthop stays in the route. Whether
		# the interface is in that route is read before del_server_route
		# touches the default routes.
		balancing_rebuild="no"
		if [ -n "$OMR_TRACKER_INTERFACE" ] && [ "$(uci -q get openmptcprouter.settings.master)" = "balancing" ] && [ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ] && [ -n "$OMR_TRACKER_DEVICE" ] && _omr_route_has_dev "$(ip route show default metric 1 2>/dev/null)" "$OMR_TRACKER_DEVICE"; then
			balancing_rebuild="yes"
		fi
		config_load openmptcprouter
		config_foreach del_server_route server
		config_foreach del_server_route6 server
		#if [ "$(uci -q get openmptcprouter.settings.master)" = "balancing" ] && [ "$(uci -q get openmptcprouter.settings.vpn)" != "mlvpn" ]; then

		if [ -n "$OMR_TRACKER_INTERFACE" ]; then
			# The rebuild is computed first: "ip route get" can pick the down
			# interface's nexthop of the balanced route, and set_route must then
			# not replace the metric 1 route by one through the first interface
			# still up when the rebuild has a route to put there. It is computed
			# and installed under the balancing lock, from the state the other
			# trackers have set by then (see _omr_balancing_lock).
			_omr_balancing_lock
			routesbalancing=""
			routesbalancingbackup=""
			if [ "$balancing_rebuild" = "yes" ]; then
				nbintf=0
				nbintfb=0
				ipv6=false
				config_load network
				config_foreach set_route_balancing interface
			fi
			setdefault="yes"
			[ -n "$routesbalancing" ] && setdefault="no"
			default_gw=$(ip route get 1.1.1.1 | grep via | awk '{print $3}')
			# "<gateway> <device>" of the IPv6 default route (see 003-up). It
			# is empty without IPv6, as is the gateway of an interface without
			# IPv6: only compare them when the interface has one.
			default_gw6=$(ip -6 route get 2606:4700:4700::1111 2>/dev/null | awk "$_omr_awk_via_dev")
			if { [ "$default_gw" = "$OMR_TRACKER_DEVICE_GATEWAY" ] || [ "$default_gw" = "" ] || { [ -n "$OMR_TRACKER_DEVICE_GATEWAY6" ] && [ "$default_gw6" = "$OMR_TRACKER_DEVICE_GATEWAY6 $OMR_TRACKER_DEVICE" ]; }; } && [ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ]; then
				[ "$(uci -q get openmptcprouter.settings.debug)" = "true" ] && _log "Default gw: $default_gw - Set routes (current: $(ip r) )"
				config_load network
				config_foreach set_route interface $OMR_TRACKER_INTERFACE "$setdefault"
				config_foreach set_route6 interface $OMR_TRACKER_INTERFACE "$setdefault"
				[ "$(uci -q get openmptcprouter.settings.debug)" = "true" ] && _log "New routes $(ip r)"
			elif [ "$(uci -q get network.$OMR_TRACKER_INTERFACE.multipath)" = "master" ]; then
				[ "$(uci -q get openmptcprouter.settings.debug)" = "true" ] && _log "Default gw: $default_gw - Set routes (current: $(ip r) )"
				config_load network
				config_foreach set_route interface $OMR_TRACKER_INTERFACE "no"
				config_foreach set_route6 interface $OMR_TRACKER_INTERFACE "no"
				[ "$(uci -q get openmptcprouter.settings.debug)" = "true" ] && _log "New routes $(ip r)"
			elif [ -n "$OMR_TRACKER_DEVICE" ] && [ -n "$(ip r show table 991337 | grep "$OMR_TRACKER_DEVICE ")" ]; then
				config_load network
				config_foreach set_route interface $OMR_TRACKER_INTERFACE "no"
				config_foreach set_route6 interface $OMR_TRACKER_INTERFACE "no"
			fi
			if [ "$balancing_rebuild" = "yes" ]; then
				if [ -n "$routesbalancing" ]; then
					_log "$OMR_TRACKER_INTERFACE down, set ip route replace default scope global metric 1 $routesbalancing"
					_omr_balanced_route_replace 1 "$routesbalancing"
				fi
				if [ -n "$routesbalancingbackup" ]; then
					_log "$OMR_TRACKER_INTERFACE down, set backup ip route replace default scope global metric 999 $routesbalancingbackup"
					_omr_balanced_route_replace 999 "$routesbalancingbackup"
				fi
			fi
			_omr_balancing_unlock
		fi
		[ "$(uci -q get openmptcprouter.settings.debug)" = "true" ] && _log "Set server routes (current: $(ip r) )"
		config_load openmptcprouter
		config_foreach set_server_all_routes server
		config_foreach set_server_all_routes6 server
		[ "$(uci -q get openmptcprouter.settings.debug)" = "true" ] && _log "Set server routes done (new: $(ip r) )"
	fi
	
	_alert_down

	if [ "$OMR_TRACKER_INTERFACE" = "glorytun" ] || [ "$OMR_TRACKER_INTERFACE" = "omrvpn" ] || [ "$OMR_TRACKER_INTERFACE" = "omr6in4" ]; then
		vpn_disabled_by_user=0
		if [ "$(uci -q get network.omrvpn.disabled)" = "1" ] || [ "$(uci -q get openmptcprouter.settings.vpn)" = "none" ]; then
			vpn_disabled_by_user=1
		fi
		if [ -z "$OMR_TRACKER_DEVICE_IP" ] && [ -z "$OMR_TRACKER_DEVICE_IP6" ]; then
			OMR_TRACKER_STATUS_MSG="$OMR_TRACKER_STATUS_MSG No IP"
		fi
		if [ "$OMR_TRACKER_STATUS_MSG" = "" ]; then
			_log "$OMR_TRACKER_INTERFACE down"
		else
			_log "$OMR_TRACKER_INTERFACE down because $OMR_TRACKER_STATUS_MSG"
		fi
		if [ "$OMR_TRACKER_INTERFACE" = "omrvpn" ]; then
			omr_state_set openmptcprouter omr vpn 'down'
		fi
		if [ "$(uci -q get openmptcprouter.settings.defaultgw)" != "0" ]; then
			del_default_route
			config_load network
			# In balancing mode the WAN trackers keep the metric 1 route
			# balanced over the WANs that are up: replacing it by a route
			# through the first of them sent all the traffic through that WAN
			# until a WAN tracker rebuilt it. Only set table 991337 then.
			setdefault="yes"
			[ "$(uci -q get openmptcprouter.settings.master)" = "balancing" ] && [ -n "$(ip route show default metric 1)" ] && setdefault="no"
			config_foreach set_route interface $OMR_TRACKER_INTERFACE "$setdefault"
			config_foreach set_route6 interface $OMR_TRACKER_INTERFACE "$setdefault"
		else
			_log "Default gateway disabled, no default gateway set"
			purge_wan_default_routes
		fi
		if [ "$vpn_disabled_by_user" != "1" ] && [ "$(uci -q get openmptcprouter.settings.shadowsocksudp)" = "1" ] && [ "$(uci -q get shadowsocks-libev.sss0.disabled)" != "1" ] && [ "$(omr_state_get openmptcprouter.omr.shadowsocks)" = "up" ] && [ "$(uci -q get shadowsocks-libev.ss_rules.redir_udp)" = "" ] && [ "$(uci -q get shadowsocks-libev.hi1.mode)" = "tcp_and_udp" ] && [ "$(uci -q get shadowsocks-libev.sss0.obfs)" != "1" ]; then
			_log "Tunnel down use ShadowSocks for UDP"
			uci -q set shadowsocks-libev.ss_rules.redir_udp='hi1'
			if /etc/init.d/shadowsocks-libev rules_exist ; then
				/etc/init.d/shadowsocks-libev rules_down >/dev/null 2>&1
				/etc/init.d/shadowsocks-libev rules_up >/dev/null 2>&1
			fi
		fi
		if [ "$vpn_disabled_by_user" = "1" ]; then
			_log "VPS/tunnel disabled by user, skip VPN restart attempts"
		else
			if [ "$(uci -q get dsvpn.vpn.enable)" = "1" ]; then
				# Shared restart cooldown with omr-schedule's 010-services
				# hook (see the MQVPN watchdog below for the full rationale)
				dsvpn_last_restart="$(cat /tmp/omr-dsvpn-last-restart 2>/dev/null)"
				[ -z "$dsvpn_last_restart" ] && dsvpn_last_restart=0
				dsvpn_now="$(date +%s)"
				if [ "$((dsvpn_now - dsvpn_last_restart))" -ge 60 ]; then
					_log "DSVPN down, restart it"
					echo "$dsvpn_now" > /tmp/omr-dsvpn-last-restart
					/etc/init.d/dsvpn restart >/dev/null 2>&1
				else
					_log "DSVPN down, restart skipped (already restarted less than 60s ago)"
				fi
			fi
			if [ "$(uci -q get mqvpn.settings.enable)" = "1" ]; then
				# Rate-limit: schedule.d/010-services' watchdog also restarts
				# MQVPN as soon as it can't find the process. Without this
				# shared cooldown, both paths can fire within the same outage
				# and restart it back-to-back, force-closing every open MPTCP
				# connection through it (confirmed live: a single MQVPN outage
				# produced 4 tun0 up/down flaps and a shadowsocks-rust crash
				# loop from the resulting scheduler-restart storm)
				mqvpn_last_restart="$(cat /tmp/omr-mqvpn-last-restart 2>/dev/null)"
				[ -z "$mqvpn_last_restart" ] && mqvpn_last_restart=0
				mqvpn_now="$(date +%s)"
				if [ "$((mqvpn_now - mqvpn_last_restart))" -ge 60 ]; then
					_log "mqvn down, restart it"
					echo "$mqvpn_now" > /tmp/omr-mqvpn-last-restart
					/etc/init.d/mqvpn restart >/dev/null 2>&1
				else
					_log "mqvn down, restart skipped (already restarted less than 60s ago)"
				fi
			fi
			if [ "$(uci -q get mlvpn.general.enable)" = "1" ]; then
				# Shared restart cooldown with omr-schedule's 010-services
				# hook (see the MQVPN watchdog above for the full rationale)
				mlvpn_last_restart="$(cat /tmp/omr-mlvpn-last-restart 2>/dev/null)"
				[ -z "$mlvpn_last_restart" ] && mlvpn_last_restart=0
				mlvpn_now="$(date +%s)"
				if [ "$((mlvpn_now - mlvpn_last_restart))" -ge 60 ]; then
					_log "MLVPN down, restart it"
					echo "$mlvpn_now" > /tmp/omr-mlvpn-last-restart
					/etc/init.d/mlvpn restart >/dev/null 2>&1
				else
					_log "MLVPN down, restart skipped (already restarted less than 60s ago)"
				fi
			fi
			if [ "$(uci -q get glorytun.vpn.enable)" = "1" ]; then
				# Shared restart cooldown with omr-schedule's 010-services
				# hook (see the MQVPN watchdog above for the full rationale)
				glorytun_last_restart="$(cat /tmp/omr-glorytun-last-restart 2>/dev/null)"
				[ -z "$glorytun_last_restart" ] && glorytun_last_restart=0
				glorytun_now="$(date +%s)"
				if [ "$((glorytun_now - glorytun_last_restart))" -ge 60 ]; then
					_log "Glorytun VPN down, restart it"
					echo "$glorytun_now" > /tmp/omr-glorytun-last-restart
					/etc/init.d/glorytun restart >/dev/null 2>&1
				else
					_log "Glorytun VPN down, restart skipped (already restarted less than 60s ago)"
				fi
			fi
			if [ "$(uci -q get glorytun-udp.vpn.enable)" = "1" ]; then
				gtudp_now="$(date +%s)"
				# When this outage began: only cleared on recovery (003-up)
				# or after a hold-down, never by a restart, so it measures
				# how long the tunnel has really been down
				gtudp_outage_start="$(cat /tmp/omr-tracker-gtudp-outage-start 2>/dev/null)"
				[ -z "$gtudp_outage_start" ] && {
					gtudp_outage_start="$gtudp_now"
					echo "$gtudp_outage_start" > /tmp/omr-tracker-gtudp-outage-start
				}
				# A path in running state means two-way keepalives with the
				# server still work: a client restart would not help (the
				# problem is elsewhere, likely server side) and each restart
				# behind a NAT consumes a new path slot on the server, so
				# don't restart unless the error persists
				gtudp_error_since="$(cat /tmp/omr-tracker-gtudp-error-since 2>/dev/null)"
				[ -z "$gtudp_error_since" ] && {
					gtudp_error_since="$gtudp_now"
					echo "$gtudp_error_since" > /tmp/omr-tracker-gtudp-error-since
				}
				# Rate-limit restarts: a restart removes all Glorytun UDP paths and
				# they are only re-added by the trackers of the other interfaces,
				# restarting again before that only creates a restart loop
				gtudp_last_restart="$(cat /tmp/omr-tracker-gtudp-last-restart 2>/dev/null)"
				[ -z "$gtudp_last_restart" ] && gtudp_last_restart=0
					gtudp_init="/etc/init.d/glorytun-udp"
					[ -x "$gtudp_init" ] || gtudp_init="glorytun-udp"
					if [ -f /tmp/omr-tracker-gtudp-holddown ]; then
					_log "Glorytun UDP VPN down, hold-down in progress (waiting for the server to purge its stale paths)"
				elif [ "$((gtudp_now - gtudp_outage_start))" -ge 900 ]; then
					# Restarts did not help for 15 minutes: the server path
					# table is probably full of stale paths. The server purges
					# all its passive paths after 2 minutes without receiving
					# anything, so stop the tunnel to stay silent long enough,
					# 004-glorytun-udp-path starts it again afterwards
					_log "Glorytun UDP VPN still down after 15 minutes of restarts, stop it for 150s so the server purges its stale paths"
					echo "$gtudp_now" > /tmp/omr-tracker-gtudp-holddown
						"$gtudp_init" stop >/dev/null 2>&1
				elif [ -n "$(glorytun-udp path 2>/dev/null | grep running)" ] && [ "$((gtudp_now - gtudp_error_since))" -lt 300 ]; then
					_log "Glorytun UDP VPN down but a path is still running, restart skipped (probably a server side issue)"
				elif [ "$((gtudp_now - gtudp_last_restart))" -ge 60 ]; then
					_log "Glorytun UDP VPN down, restart it"
					echo "$gtudp_now" > /tmp/omr-tracker-gtudp-last-restart
					# Restart the persistent error delay so with a running
					# path next restart is only after another 5 minutes
					echo "$gtudp_now" > /tmp/omr-tracker-gtudp-error-since
						"$gtudp_init" restart >/dev/null 2>&1
				else
					_log "Glorytun UDP VPN down, restart skipped (restarted less than 60s ago, waiting for paths to be set again)"
				fi
			fi
			if [ "$(uci -q get openvpn.omr.enabled)" = "1" ] && [ "$(uci -q get openmptcprouter.settings.vpn)" != "none" ]; then
				# Shared restart cooldown with omr-schedule's 010-services
				# hook (see the MQVPN watchdog above for the full rationale)
				openvpn_last_restart="$(cat /tmp/omr-openvpn-last-restart 2>/dev/null)"
				[ -z "$openvpn_last_restart" ] && openvpn_last_restart=0
				openvpn_now="$(date +%s)"
				if [ "$((openvpn_now - openvpn_last_restart))" -ge 60 ]; then
					_log "OpenVPN down, restart it"
					echo "$openvpn_now" > /tmp/omr-openvpn-last-restart
					/etc/init.d/openvpn restart omr >/dev/null 2>&1
				else
					_log "OpenVPN down, restart skipped (already restarted less than 60s ago)"
				fi
			fi
			# NOTE: this uci package is "softethervpn", not "ethersoftvpn" --
			# every other caller in the tree (omr-schedule, omr-6in4,
			# openmptcprouter-vps) uses "softethervpn.openmptcprouter.enable".
			# The old key here never matched anything, so this branch was
			# dead code.
			if [ "$(uci -q get softethervpn.openmptcprouter.enable)" = "1" ]; then
				# Shared restart cooldown with omr-schedule's 010-services
				# hook (see the MQVPN watchdog above for the full rationale)
				softethervpn_last_restart="$(cat /tmp/omr-softethervpn-last-restart 2>/dev/null)"
				[ -z "$softethervpn_last_restart" ] && softethervpn_last_restart=0
				softethervpn_now="$(date +%s)"
				if [ "$((softethervpn_now - softethervpn_last_restart))" -ge 60 ]; then
					_log "SoftEther VPN down, restart it"
					echo "$softethervpn_now" > /tmp/omr-softethervpn-last-restart
					/etc/init.d/softethervpnclient restart >/dev/null 2>&1
				else
					_log "SoftEther VPN down, restart skipped (already restarted less than 60s ago)"
				fi
			fi
		fi
		config_load openmptcprouter
		[ "$(uci -q get openmptcprouter.settings.pihole_auto_conf)" != "0" ] && config_foreach disable_pihole server
		#if [ "$(uci -q get openmptcprouter.settings.master)" != "balancing" ] || [ "$(uci -q get openmptcprouter.settings.vpn)" = "mlvpn" ]; then
		if [ "$(uci -q get openmptcprouter.settings.master)" != "balancing" ]; then
			config_foreach set_server_default_route server
			config_foreach set_server_default_route6 server
		fi
	fi
	restart_down=""
	if [ -n "$OMR_TRACKER_INTERFACE" ]; then
		restart_down="$(uci -q get omr-tracker.$OMR_TRACKER_INTERFACE.restart_down)"
	fi
	[ -z "$restart_down" ] && restart_down="$(uci -q get omr-tracker.defaults.restart_down)"
	[ "$restart_down" = "1" ] && [ "$quota_cut" != "true" ] && {
		_log "Restart $OMR_TRACKER_INTERFACE"
		ifdown $OMR_TRACKER_INTERFACE >/dev/null 2>&1
		sleep 5
		ifup $OMR_TRACKER_INTERFACE >/dev/null 2>&1
	}

	dns_flush
	if [ -n "$OMR_TRACKER_INTERFACE" ]; then
		omr_state_del openmptcprouter $OMR_TRACKER_INTERFACE lc
	fi
	# Nothing to commit: this script only writes runtime state (/var/state),
	# and committing here took the changes other processes had pending in
	# the shared /tmp/.uci along with nothing of its own
	#ubus call network reload

	# Set a little sleep after an interface error
#	if [ -n "$RANDOM" ]; then
#		sleep `expr $RANDOM % 100`
#	else
#		sleep `awk 'BEGIN{srand();print int(rand()*20)}'`
#	fi

	exit 0
fi

exit 0
