#!/bin/sh /etc/rc.common
# shellcheck disable=SC2039
# vim: set noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 :
# Copyright (C) 2018-2025 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
# Released under GPL 3. See LICENSE for the full terms.

# shellcheck disable=SC2034
{
	START=90
	USE_PROCD=1
	EXTRA_COMMANDS="start_interface failback failback_status"
	EXTRA_HELP="	failback	Move back to the master server now, when the failback setting keeps a backup in use
	failback_status	Print \"held\", then the master and the backup in use, when a backup is kept in use"
}

. /usr/lib/unbound/iptools.sh
. "${OMR_LIB_DIR:-/usr/share/omr/lib}/omr-state.sh"
. /lib/functions/network.sh

_validate_section() {
	local tmp_timeout=$timeout tmp_count=$count tmp_tries=$tries tmp_size=$size tmp_max_ttl=$max_ttl tmp_failure_loss=$failure_loss tmp_failure_latency=$failure_latency tmp_recovery_loss=$recovery_loss tmp_recovery_latency=$recovery_latency tmp_failure_congestion=$failure_congestion tmp_recovery_congestion=$recovery_congestion tmp_reliability=$reliability tmp_country=$country
	local tmp_interval=$interval tmp_interval_tries=$interval_tries tmp_options=$options tmp_type=$type tmp_enabled=$enabled tmp_wait_test=$wait_test tmp_server_http_test=$server_http_test tmp_server_test=$server_test tmp_check_quality=$check_quality tmp_failure_interval=$failure_interval tmp_tries_up=$tries_up tmp_family=$family tmp_post_interval=$post_interval tmp_post_interval_down=$post_interval_down
	local tmp_latency_ip=$latency_ip tmp_latency_ip6=$latency_ip6 tmp_monitor_only=$monitor_only

	# No default value here for an option the defaults section can set:
	# validate_data gives the default of an option a section doesn't set, so
	# for an interface without its own value, the default would replace the
	# value of the defaults section (server_test, check_quality, ...)
	uci_validate_section omr-tracker "$1" "$2" \
		'timeout:uinteger'  \
		'size:uinteger'  \
		'max_ttl:uinteger'  \
		'failure_loss:uinteger'  \
		'failure_latency:uinteger'  \
		'recovery_loss:uinteger'  \
		'recovery_latency:uinteger'  \
		'failure_congestion:uinteger'  \
		'recovery_congestion:uinteger'  \
		'check_quality:bool' \
		'count:uinteger'  \
		'reliability:uinteger'  \
		'tries:uinteger'    \
		'tries_up:uinteger'    \
		'interval:uinteger' \
		'interval_tries:uinteger' \
		'post_interval:uinteger' \
		'post_interval_down:uinteger' \
		'failure_interval:uinteger' \
		'wait_test:uinteger' \
		'type:string:undef'  \
		'enabled:bool'    \
		'server_http_test:bool'    \
		'server_test:bool'    \
		'family:string'   \
		'options:string'  \
		'country:string'  \
		'latency_ip:host' \
		'latency_ip6:host' \
		'monitor_only:bool'

	[ -z "$timeout"  ] && timeout=$tmp_timeout
	[ -z "$count"  ] && count=$tmp_count
	[ -z "$reliability"  ] && reliability=$tmp_reliability
	[ -z "$size"  ] && size=$tmp_size
	[ -z "$failure_loss"  ] && failure_loss=$tmp_failure_loss
	[ -z "$failure_latency"  ] && failure_latency=$tmp_failure_latency
	[ -z "$failure_interval"  ] && failure_interval=$tmp_failure_interval
	[ -z "$recovery_loss"  ] && recovery_loss=$tmp_recovery_loss
	[ -z "$recovery_latency"  ] && recovery_latency=$tmp_recovery_latency
	[ -z "$failure_congestion"  ] && failure_congestion=$tmp_failure_congestion
	[ -z "$recovery_congestion"  ] && recovery_congestion=$tmp_recovery_congestion
	[ -z "$check_quality"  ] && check_quality=$tmp_check_quality
	[ -z "$max_ttl"  ] && max_ttl=$tmp_max_ttl
	[ -z "$tries"    ] && tries=$tmp_tries
	[ -z "$tries_up"    ] && tries_up=$tmp_tries_up
	[ -z "$interval" ] && interval=$tmp_interval
	[ -z "$post_interval" ] && post_interval=$tmp_post_interval
	[ -z "$post_interval_down" ] && post_interval_down=$tmp_post_interval_down
	[ -z "$interval_tries" ] && interval_tries=$tmp_interval_tries
	[ -z "$wait_test" ] && wait_test=$tmp_wait_test
	[ -z "$options"  ] && options=$tmp_options
	[ "$type" = "undef" ] && type=${tmp_type:-ping}
	[ -z "$server_http_test"  ] && server_http_test=$tmp_server_http_test
	[ -z "$server_test"  ] && server_test=$tmp_server_test
	[ -z "$family"  ] && family=$tmp_family
	[ -z "$enabled"  ] && enabled=$tmp_enabled
	[ -z "$country"  ] && country=$tmp_country
	[ -z "$latency_ip"  ] && latency_ip=$tmp_latency_ip
	[ -z "$latency_ip6"  ] && latency_ip6=$tmp_latency_ip6
	[ -z "$monitor_only"  ] && monitor_only=$tmp_monitor_only
}

_validate_section_hosts() {
	local tmp_hosts=$hosts tmp_hosts6=$hosts6

	uci_validate_section omr-tracker "$1" "$2" \
	'hosts:list(host)'  \
	'hosts6:list(host)'

	[ -z "$hosts"    ] && hosts=$tmp_hosts
	[ -z "$hosts6"    ] && hosts6=$tmp_hosts6
}


# The proxy section's host group, world_proxy without one. "world" (a WAN
# hosts_defaults group, not a hosts_proxy one) was accepted by older pages.
_proxy_country() {
	case "$country" in
		''|world) country="world_proxy" ;;
	esac
}

# The failure a monitor only tracker reported (see 002-error): nothing
# clears it once the interface is tracked normally again or not tracked at
# all, and the status page kept showing the interface in error
_clear_monitor_state() {
	omr_state_del openmptcprouter "$1" monitor_state
}

# _restore_balancing <interface>: in balancing mode 002-error rebuilt the
# balanced default routes without the down interface, and 003-up, which
# puts it back on a recovery, doesn't run for an interface no longer
# tracked. They are rebuilt as 003-up does, from every interface's state,
# under the same lock. In bash: the trackers' library needs it.
_restore_balancing() {
	[ "$(uci -q get openmptcprouter.settings.master)" = "balancing" ] || return 0
	[ "$(uci -q get openmptcprouter.settings.defaultgw)" = "0" ] && return 0
	command -v bash >/dev/null 2>&1 || return 0
	bash -c '
		. /lib/functions.sh
		. "${OMR_LIB_DIR:-/usr/share/omr/lib}/common-post-tracking.sh"
		_log() { logger -t "omr-tracker" "$*"; }
		_omr_balancing_lock
		routesbalancing="" routesbalancingbackup="" nbintf=0 nbintfb=0 ipv6=false
		config_load network
		config_foreach set_route_balancing interface
		if [ -n "$routesbalancing" ] && ! _default_route_matches 1 "$routesbalancing" "$nbintf" false; then
			_log "$1 is no longer tracked, set ip route replace default scope global metric 1 $routesbalancing"
			_omr_balanced_route_replace 1 "$routesbalancing"
		fi
		if [ -n "$routesbalancingbackup" ] && ! _default_route_matches 999 "$routesbalancingbackup" "$nbintfb" false; then
			_log "$1 is no longer tracked, set backup ip route replace default scope global metric 999 $routesbalancingbackup"
			_omr_balanced_route_replace 999 "$routesbalancingbackup"
		fi
		_omr_balancing_unlock
	' _restore_balancing "$1"
}

# _release_down <interface> [<device>]: the interface is no longer tracked
# while its tracker had it down. "state" down then stayed until a reboot,
# nothing else clears it, and mptcp, omr-bypass, omr-quota and keepalived-ha
# kept the interface out of use. Its tracker, when there was one, only stops
# once procd has the new instances: wait for it, its last post-tracking run
# could set the state down again.
_release_down() {
	local _wait=0 _dev="$2"
	while _tracker_running "$1" && [ "$_wait" -lt 35 ]; do
		sleep 1
		_wait=$((_wait + 1))
	done
	# Still running: tracked again meanwhile
	_tracker_running "$1" && return 0
	[ "$(omr_state_get "openmptcprouter.$1.state")" = "down" ] || return 0
	omr_state_del openmptcprouter "$1" state
	logger -t "omr-tracker" "$1 is no longer tracked, it is not kept down any more"
	# omr-bypass's copy of the last tracker status (its 025 hook), then its
	# output WAN lists and source policies, which only avoid a down WAN
	rm -f "/var/run/omr-bypass-failover/track.$1"
	[ -x /usr/lib/omr-bypass/wan-failover ] && /usr/lib/omr-bypass/wan-failover hotplug "$1" >/dev/null 2>&1 </dev/null
	_restore_balancing "$1" >/dev/null 2>&1 </dev/null
	# Back in MPTCP as configured: what 003-up does on a recovery. The mptcp
	# init skipped it while the state was down.
	if [ -z "$_dev" ]; then
		_dev=$(ifstatus "$1" 2>/dev/null | jsonfilter -q -e '@["l3_device"]')
		case "$_dev" in
			*@*) _dev=$(ifstatus "$1" 2>/dev/null | jsonfilter -q -e '@["device"]') ;;
		esac
	fi
	[ -n "$_dev" ] && [ -x /etc/init.d/mptcp ] && /etc/init.d/mptcp reload "$_dev" >/dev/null 2>&1
	return 0
}

# _untracked <interface> [<device>]: no tracker is launched for the interface
_untracked() {
	_clear_monitor_state "$1"
	[ "$(omr_state_get "openmptcprouter.$1.state")" = "down" ] || return 0
	# Detached: it waits for the tracker to stop, which this script must not
	( _release_down "$1" "$2" </dev/null >/dev/null 2>&1 & )
}

_launch_tracker() {
	# *_6: DHCPv6 companion interfaces are tracked through their parent
	case "$1" in
		loopback|lan*|if0*|*_6) return;;
	esac
	[ -z "$1" ] && return
	# Every option _validate_section reads: one left global leaked from an
	# interface into the next ones (check_quality, country...) and into the
	# proxy trackers
	local hosts hosts6 timeout count tries tries_up interval interval_tries post_interval post_interval_down options type enabled wait_test ipv6 proto server_http_test server_test check_quality country size max_ttl failure_loss failure_interval failure_latency recovery_loss recovery_latency failure_congestion recovery_congestion family reliability latency_ip latency_ip6 monitor_only
	_validate_section "defaults" "defaults"
	_validate_section "interface" "$1"
	[ -z "$country" ] && country="world"
	_validate_section_hosts "hosts_defaults" "$country"


	local ifname="" ip4table
	#network_get_device ifname $1
	#[ -z "$ifname" ] && network_get_physdev ifname $1
	[ -z "$ifname" ] && ifname=$(ifstatus "$1" | jsonfilter -q -e '@["l3_device"]')
	[ -z "$ifname" ] && ifname=$(ifstatus "$1_4" | jsonfilter -q -e '@["l3_device"]')
	#[ -z "$ifname" ] && config_get ifname "$1" device
	[ -n "$(echo $ifname | grep '@')" ] && ifname=$(ifstatus "$1" | jsonfilter -q -e '@["device"]')

	config_get multipath "$1" multipath
	config_get ifenabled "$1" auto
	config_get gateway "$1" gateway
	config_get ipv6 "$1" ipv6
	config_get proto "$1" proto
	config_get disabled "$1" disabled

	#[ -z "$ifname" ] || [ -z "$multipath" ] || [ "$multipath" = "off" ] && [ "$1" != "glorytun" ] && [ "$1" != "omrvpn" ] && [ "$( uci -q get openmptcprouter.$1.multipathvpn)" != "1" ] && return
	([ -z "$multipath" ] || [ "$multipath" = "off" ]) && [ "$1" != "glorytun" ] && [ "$1" != "omrvpn" ] && [ "$( uci -q get openmptcprouter.$1.multipathvpn)" != "1" ] && { _untracked "$1" "$ifname"; return; }
	[ "$1" = "omrvpn" ] && [ "$(uci -q get openmptcprouter.settings.vpn)" = "none" ] && { _untracked "$1" "$ifname"; return; }
	[ "${ifenabled}" = "0" ] && { _untracked "$1" "$ifname"; return; }
	[ "${enabled}" = "0" ] && { _untracked "$1" "$ifname"; return; }
	[ "${disabled}" = "1" ] && { _untracked "$1" "$ifname"; return; }
	[ -z "${hosts}" ] && [ -z "${latency_ip}" ] && [ "$type" != "none" ] && { _untracked "$1" "$ifname"; return; }
	[ "$monitor_only" = "1" ] || _clear_monitor_state "$1"
	ifstatus=$(ifstatus "$1" | jsonfilter -q -e '@["up"]')
	ifdevice=$(ifstatus "$1" | jsonfilter -q -e '@["device"]')
	#[ "${ifstatus}" = "false" ] && [ -z "${ifdevice}" ] && return
	[ -z "${interval}" ] && interval=2
	[ -z "${interval_tries}" ] && interval_tries=1
	[ -z "${post_interval}" ] && post_interval=10
	# How much less often the post-tracking.d cascade's periodic refresh work
	# (metrics, wifi/modem signal, tc/bbr stats, ...) runs while the link is
	# down: no point paying that fork cost at the healthy-link cadence for a
	# dead link, since state transitions still trigger it immediately.
	[ -z "${post_interval_down}" ] && post_interval_down=$((post_interval * 6))
	[ -z "${count}" ] && count=2
	[ -z "${max_ttl}" ] && max_ttl=60
	[ -z "${size}" ] && size=56
	[ -z "${check_quality}" ] && check_quality=0
	[ -z "${tries}" ] && tries=5
	[ -z "${reliability}" ] && reliability=1
	[ -z "${tries_up}" ] && tries_up=${tries}
	[ "$(uci -q get openmptcprouter.settings.disable_ipv6)" = "1" ] && ipv6="0"
	[ -z "${family}" ] && family='ipv4'
	# LuCI removes an emptied field, the defaults section's included: without
	# a value, "ping -w ''" failed every probe, and an empty failure interval
	# let the tracker test again at once, without a pause, while down
	[ -z "${timeout}" ] && timeout=4
	[ -z "${failure_interval}" ] && failure_interval=20
	# The thresholds LuCI shows for a quality check without its own: the
	# tracker skips a check without a threshold, so a ticked "Check link
	# quality" left alone checked nothing
	if [ "${check_quality}" = "1" ]; then
		[ -z "${failure_latency}" ] && failure_latency=1000
		[ -z "${failure_loss}" ] && failure_loss=40
		[ -z "${recovery_latency}" ] && recovery_latency=500
		[ -z "${recovery_loss}" ] && recovery_loss=10
	fi

	procd_open_instance "$1"
	# shellcheck disable=SC2086
	procd_set_param command /bin/omr-tracker "$1" $options
	procd_append_param env "OMR_TRACKER_HOSTS=$hosts"
	procd_append_param env "OMR_TRACKER_HOSTS6=$hosts6"
	procd_append_param env "OMR_TRACKER_LATENCY_IP=$latency_ip"
	procd_append_param env "OMR_TRACKER_LATENCY_IP6=$latency_ip6"
	procd_append_param env "OMR_TRACKER_TIMEOUT=$timeout"
	procd_append_param env "OMR_TRACKER_SIZE=$size"
	procd_append_param env "OMR_TRACKER_CHECK_QUALITY=$check_quality"
	procd_append_param env "OMR_TRACKER_MAX_TTL=$max_ttl"
	procd_append_param env "OMR_TRACKER_FAILURE_LOSS=$failure_loss"
	procd_append_param env "OMR_TRACKER_FAILURE_LATENCY=$failure_latency"
	procd_append_param env "OMR_TRACKER_RECOVERY_LOSS=$recovery_loss"
	procd_append_param env "OMR_TRACKER_RECOVERY_LATENCY=$recovery_latency"
	procd_append_param env "OMR_TRACKER_FAILURE_CONGESTION=$failure_congestion"
	procd_append_param env "OMR_TRACKER_RECOVERY_CONGESTION=$recovery_congestion"
	procd_append_param env "OMR_TRACKER_COUNT=$count"
	procd_append_param env "OMR_TRACKER_RELIABILITY=$reliability"
	procd_append_param env "OMR_TRACKER_TRIES=$tries"
	procd_append_param env "OMR_TRACKER_TRIES_UP=$tries_up"
	procd_append_param env "OMR_TRACKER_INTERVAL=$interval"
	procd_append_param env "OMR_TRACKER_FAILURE_INTERVAL=$failure_interval"
	procd_append_param env "OMR_TRACKER_INTERVAL_TRIES=$interval_tries"
	procd_append_param env "OMR_TRACKER_POST_INTERVAL=$post_interval"
	procd_append_param env "OMR_TRACKER_POST_INTERVAL_DOWN=$post_interval_down"
	procd_append_param env "OMR_TRACKER_TABLE=$ip4table"
	procd_append_param env "OMR_TRACKER_DEVICE=$ifname"
	procd_append_param env "OMR_TRACKER_DEVICE_GATEWAY=$gateway"
	procd_append_param env "OMR_TRACKER_TYPE=$type"
	procd_append_param env "OMR_TRACKER_FAMILY=$family"
	procd_append_param env "OMR_TRACKER_IPV6=$ipv6"
	procd_append_param env "OMR_TRACKER_PROTO=$proto"
	procd_append_param env "OMR_TRACKER_WAIT_TEST=$wait_test"
	procd_append_param env "OMR_TRACKER_SERVER_HTTP_TEST=$server_http_test"
	procd_append_param env "OMR_TRACKER_SERVER_TEST=$server_test"
	procd_append_param env "OMR_TRACKER_MONITOR_ONLY=${monitor_only:-0}"
	procd_set_param limits nofile="51200 51200"
	procd_set_param respawn 0 10 0
	# A tracker stopped while it runs a post-tracking hook exits once the
	# hook is done. With procd's default of 5 s, a slower hook was left
	# running after the KILL, next to the hooks of the new tracker.
	procd_set_param term_timeout 30
	procd_set_param stderr 1
	procd_close_instance
	sleep 1
}

_launch_server_tracker() {
	local hosts timeout tries interval interval_tries options type enabled wait_test
	_validate_section "defaults" "defaults"
	_validate_section "server" "server"

	# Without the tracker, nothing would end a failback hold, and
	# openmptcprouter-vps would leave the master alone for good
	[ "${enabled}" = "0" ] && { omr_state_del openmptcprouter omr failback_hold; return; }
	[ -z "${interval_tries}" ] && interval_tries=1

	procd_open_instance
	# shellcheck disable=SC2086
	procd_set_param command /bin/omr-tracker-server "$1" $options
	procd_append_param env "OMR_TRACKER_TIMEOUT=$timeout"
	procd_append_param env "OMR_TRACKER_TRIES=$tries"
	procd_append_param env "OMR_TRACKER_INTERVAL=$interval"
	procd_append_param env "OMR_TRACKER_INTERVAL_TRIES=$interval_tries"
	procd_append_param env "OMR_TRACKER_WAIT_TEST=$wait_test"
	procd_append_param env "OMR_TRACKER_TYPE=$type"
	procd_append_param env "OMR_TRACKER_FAILBACK=$(uci -q get omr-tracker.server.failback)"
	procd_append_param env "OMR_TRACKER_FAILBACK_DELAY=$(uci -q get omr-tracker.server.failback_delay)"
	procd_append_param env "OMR_TRACKER_BACKUP_CHECK_INTERVAL=$(uci -q get omr-tracker.server.backup_check_interval)"
	procd_set_param limits nofile="51200 51200"
	procd_set_param respawn 0 10 0
	procd_set_param stderr 1
	procd_close_instance
	sleep 1
}

_launch_gre_tracker() {
	local hosts timeout tries interval interval_tries options type enabled wait_test
	_validate_section "defaults" "defaults"
	_validate_section "gre" "gre"

	[ "${enabled}" = "0" ] && return
	[ -z "${interval_tries}" ] && interval_tries=1

	procd_open_instance
	# shellcheck disable=SC2086
	procd_set_param command /bin/omr-tracker-gre "$1" $options
	procd_append_param env "OMR_TRACKER_TIMEOUT=$timeout"
	procd_append_param env "OMR_TRACKER_TRIES=$tries"
	procd_append_param env "OMR_TRACKER_INTERVAL=$interval"
	procd_append_param env "OMR_TRACKER_INTERVAL_TRIES=$interval_tries"
	procd_append_param env "OMR_TRACKER_WAIT_TEST=$wait_test"
	procd_set_param limits nofile="51200 51200"
	procd_set_param respawn 0 10 0
	procd_set_param stderr 1
	procd_close_instance
	sleep 1
}

_initialize_shadowsocks_tracker() {
	local redir_tcp server tracker_server
	server=$1

	[ -z "$server" ] && return
	[ -n "$(echo $server | grep sss)" ] || return
	[ "$(uci -q get openmptcprouter.settings.proxy)" = "none" ] && return

	#redir_tcp=$(uci -q get shadowsocks-libev.ss_rules.redir_tcp)
	#config_get tracker_server ss_rules server
	config_get ss_disabled $server disabled 0
	[ "$ss_disabled" = "0" ] && ss_enable="1"
	[ -z "$(uci -q get shadowsocks-libev.tracker_${server})" ] && [ "$ss_disabled" != "1" ] && {
		logger -t "omr-tracker" "Create ShadowSock tracker ss_local..."
		uci -q batch <<-EOF >/dev/null
		    set shadowsocks-libev.tracker_${server}=ss_local
		    set shadowsocks-libev.tracker_${server}.server=$server
		    set shadowsocks-libev.tracker_${server}.local_address="127.0.0.1"
		    set shadowsocks-libev.tracker_${server}.local_port=${count}
		    set shadowsocks-libev.tracker_${server}.mode=tcp_and_udp
		    set shadowsocks-libev.tracker_${server}.timeout=600
		    set shadowsocks-libev.tracker_${server}.fast_open=1
		    set shadowsocks-libev.tracker_${server}.syslog=0
		    set shadowsocks-libev.tracker_${server}.reuse_port=1
		    set shadowsocks-libev.tracker_${server}.mptcp=1
		    set shadowsocks-libev.tracker_${server}.verbose=0
		    commit shadowsocks-libev
		EOF
		logger -t "omr-tracker" "Restart ShadowSocks"
		/etc/init.d/shadowsocks-libev restart
	}
#	[ -n "$tracker_server" ] && [ "$server" = "$tracker_server" ] || {
#		logger -t "omr-tracker" "Set ShadowSock tracker to current server ($tracker_server -> $server)..."
#		uci -q batch <<-EOF >/dev/null
#		    set shadowsocks-libev.tracker.server=$server
#		    commit shadowsocks-libev
#		EOF
#		/etc/init.d/shadowsocks-libev restart
#	}
	count=$((count+1))
}

_initialize_shadowsocks_rust_tracker() {
	local redir_tcp server tracker_server
	server=$1

	[ -z "$server" ] && return
	[ -n "$(echo $server | grep sss)" ] || return
	[ "$(uci -q get openmptcprouter.settings.proxy)" = "none" ] && return

	#redir_tcp=$(uci -q get shadowsocks-libev.ss_rules.redir_tcp)
	#config_get tracker_server ss_rules server
	config_get ss_rust_disabled $server disabled 0
	[ "$ss_rust_disabled" = "0" ] && ss_rust_enable="1"
	[ -z "$(uci -q get shadowsocks-rust.tracker_${server})" ] && [ "$ss_rust_disabled" != "1" ] && {
		logger -t "omr-tracker" "Create ShadowSock tracker ss_local..."
		uci -q batch <<-EOF >/dev/null
		    set shadowsocks-rust.tracker_${server}=ss_local
		    set shadowsocks-rust.tracker_${server}.server=$server
		    set shadowsocks-rust.tracker_${server}.local_address="127.0.0.1"
		    set shadowsocks-rust.tracker_${server}.local_port=${count}
		    set shadowsocks-rust.tracker_${server}.mode=tcp_and_udp
		    set shadowsocks-rust.tracker_${server}.timeout=600
		    set shadowsocks-rust.tracker_${server}.fast_open=0
		    set shadowsocks-rust.tracker_${server}.syslog=0
		    set shadowsocks-rust.tracker_${server}.reuse_port=1
		    set shadowsocks-rust.tracker_${server}.mptcp=1
		    set shadowsocks-rust.tracker_${server}.verbose=0
		    commit shadowsocks-rust
		EOF
		logger -t "omr-tracker" "Restart ShadowSocks"
		/etc/init.d/shadowsocks-rust restart
	}
	count=$((count+1))
}

_launch_shadowsocks_tracker() {
	local hosts hosts6 timeout tries interval local_port enabled server wait_test country

	[ "$(echo $1 | grep tracker)" != "" ] || return
	[ "$(uci -q get openmptcprouter.settings.proxy)" = "none" ] && return

	_validate_section "proxy" "proxy"
	_proxy_country
	_validate_section_hosts "hosts_proxy" "$country"
	
	config_get local_port "$1" local_port
	local disabled
	config_get disabled "$1" disabled 0
	config_get server "$1" server
	#echo "enabled: $enabled - disabled: $disabled - host: $hosts"
	{ [ "$enabled" = "0" ] || [ "$disabled" = "1" ] || [ -z "$hosts" ]; } && return
	[ -z "$server" ] && return
	[ "$(uci -q get shadowsocks-libev.${server}.disabled)" = "1" ] && return
	redir_status=false
	__ss_redir_status() {
		[ "$(uci -q get shadowsocks-libev.$1.server)" = "$2" ] && [ "$(uci -q get shadowsocks-libev.$1.disabled)" != "1" ] && redir_status=true
	}
	config_foreach __ss_redir_status ss_redir $server
	#[ $redir_status = false ] && return
	procd_open_instance
	# shellcheck disable=SC2086
	procd_set_param command /bin/omr-tracker-ss "$1"
	procd_append_param env "OMR_TRACKER_HOSTS=$hosts"
	procd_append_param env "OMR_TRACKER_HOSTS6=$hosts6"
	procd_append_param env "OMR_TRACKER_TIMEOUT=$timeout"
	procd_append_param env "OMR_TRACKER_TRIES=$tries"
	procd_append_param env "OMR_TRACKER_INTERVAL=$interval"
	procd_append_param env "OMR_TRACKER_PROXY=127.0.0.1:$local_port"
	procd_append_param env "OMR_TRACKER_WAIT_TEST=$wait_test"
	procd_append_param env "OMR_TRACKER_SERVER=$server"
	procd_append_param env "OMR_TRACKER_SS_TYPE=libev"
	procd_set_param limits nofile="51200 51200"
	procd_set_param respawn 0 10 0
	procd_set_param stderr 1
	procd_close_instance
	sleep 1
}

_launch_shadowsocks_rust_tracker() {
	local hosts hosts6 timeout tries interval local_port enabled server wait_test country

	[ "$(echo $1 | grep tracker)" != "" ] || return
	[ "$(uci -q get openmptcprouter.settings.proxy)" = "none" ] && return

	_validate_section "proxy" "proxy"
	_proxy_country
	_validate_section_hosts "hosts_proxy" "$country"
	
	config_get local_port "$1" local_port
	local disabled
	config_get disabled "$1" disabled 0
	config_get server "$1" server

	{ [ "$enabled" = "0" ] || [ "$disabled" = "1" ] || [ -z "$hosts" ]; } && return
	[ -z "$server" ] && return
	[ "$(uci -q get shadowsocks-rust.${server}.disabled)" = "1" ] && return
	redir_status=false
	__ss_redir_status() {
		[ "$(uci -q get shadowsocks-rust.$1.server)" = "$2" ] && [ "$(uci -q get shadowsocks-rust.$1.disabled)" != "1" ] && redir_status=true
	}
	config_foreach __ss_redir_status ss_redir $server
	#[ $redir_status = false ] && return
	
	procd_open_instance
	# shellcheck disable=SC2086
	procd_set_param command /bin/omr-tracker-ss "$1"
	procd_append_param env "OMR_TRACKER_HOSTS=$hosts"
	procd_append_param env "OMR_TRACKER_HOSTS6=$hosts6"
	procd_append_param env "OMR_TRACKER_TIMEOUT=$timeout"
	procd_append_param env "OMR_TRACKER_TRIES=$tries"
	procd_append_param env "OMR_TRACKER_INTERVAL=$interval"
	procd_append_param env "OMR_TRACKER_PROXY=127.0.0.1:$local_port"
	procd_append_param env "OMR_TRACKER_WAIT_TEST=$wait_test"
	procd_append_param env "OMR_TRACKER_SERVER=$server"
	procd_append_param env "OMR_TRACKER_SS_TYPE=rust"
	procd_set_param limits nofile="51200 51200"
	procd_set_param respawn 0 10 0
	procd_set_param stderr 1
	procd_close_instance
	sleep 1
}

_launch_v2ray_tracker() {
	local hosts timeout tries interval local_port enabled server wait_test country
	
	_validate_section "proxy" "proxy"
	_proxy_country
	_validate_section_hosts "hosts_proxy" "$country"

	{ [ "$enabled" = "0" ] || [ -z "$hosts" ]; } && return

	procd_open_instance
	# shellcheck disable=SC2086
	procd_set_param command /bin/omr-tracker-v2ray "$1"
	procd_append_param env "OMR_TRACKER_HOSTS=$hosts"
	procd_append_param env "OMR_TRACKER_TIMEOUT=$timeout"
	procd_append_param env "OMR_TRACKER_TRIES=$tries"
	procd_append_param env "OMR_TRACKER_INTERVAL=$interval"
	procd_append_param env "OMR_TRACKER_PROXY=127.0.0.1:1111"
	procd_append_param env "OMR_TRACKER_WAIT_TEST=$wait_test"
	procd_set_param limits nofile="51200 51200"
	procd_set_param respawn 0 10 0
	procd_set_param stderr 1
	procd_close_instance
	sleep 1
}

_launch_xray_tracker() {
	local hosts timeout tries interval local_port enabled server wait_test country
	
	_validate_section "proxy" "proxy"
	_proxy_country
	_validate_section_hosts "hosts_proxy" "$country"
	{ [ "$enabled" = "0" ] || [ -z "$hosts" ]; } && return

	procd_open_instance
	# shellcheck disable=SC2086
	procd_set_param command /bin/omr-tracker-xray "$1"
	procd_append_param env "OMR_TRACKER_HOSTS=$hosts"
	procd_append_param env "OMR_TRACKER_TIMEOUT=$timeout"
	procd_append_param env "OMR_TRACKER_TRIES=$tries"
	procd_append_param env "OMR_TRACKER_INTERVAL=$interval"
	procd_append_param env "OMR_TRACKER_PROXY=127.0.0.1:1111"
	procd_append_param env "OMR_TRACKER_WAIT_TEST=$wait_test"
	procd_set_param limits nofile="51200 51200"
	procd_set_param respawn 0 10 0
	procd_set_param stderr 1
	procd_close_instance
	sleep 1
}

_have_server() {
	local ip disabled
	config_get ip "$1" ip
	config_get disabled "$1" disabled
	[ -n "$ip" ] && [ "$disabled" != "1" ] && haveserver=true
}

_gre_tunnel() {
	config_get proto $1 proto
	[ "$proto" = "gre" ] && gretunnel=true
}

# Remove post-tracking lock directories left behind by killed runs.
# With an interface argument only that interface's locks are removed,
# without argument all of them are.
_cleanup_locks() {
	local iface
	if [ -n "$1" ]; then
		iface="$(printf '%s' "$1" | tr -c 'A-Za-z0-9_.-' '_')"
		rm -rf "/var/run/omr-tracker/003-up.${iface}.lock" 2>/dev/null
	else
		rm -rf /var/run/omr-tracker/*.lock 2>/dev/null
	fi
}

# True when <command line> is that of a tracker daemon for <interface>.
# The interface is the tracker's last argument, so the match has to be on the
# whole "omr-tracker <interface>" token: a plain substring test also answers
# yes for "wan" while only wan1 is running.
_tracker_cmdline_matches() {
	case " $1 " in
		*"omr-tracker $2 "*) return 0 ;;
	esac
	return 1
}

# Is any tracker daemon (interface, proxy, server or GRE) still running?
_any_tracker_running() {
	local pid ppid
	for pid in $(pgrep -f '/bin/omr-tracker'); do
		ppid="$(awk '/^PPid:/ {print $2}' "/proc/$pid/status" 2>/dev/null)"
		[ "$ppid" = "1" ] && return 0
	done
	return 1
}

# Is a tracker daemon already running for interface $1?
_tracker_running() {
	local pid ppid
	for pid in $(pgrep -f omr-tracker); do
		# stderr is redirected first on purpose: a pid that exits between the
		# pgrep and this read makes the shell, not tr, report the open error.
		_tracker_cmdline_matches "$(tr '\0' ' ' 2>/dev/null < "/proc/$pid/cmdline")" "$1" || continue
		ppid="$(awk '/^PPid:/ {print $2}' "/proc/$pid/status" 2>/dev/null)"
		[ "$ppid" = "1" ] && return 0
	done
	return 1
}

# Asks omr-tracker-server to move back to the master on its next round
failback() {
	if [ "$(omr_state_get openmptcprouter.omr.failback_hold)" != "1" ]; then
		echo "No backup server is kept in use while the master server answers"
		return 1
	fi
	omr_state_set openmptcprouter omr failback_now 1
}

_failback_status_server() {
	local disabled master backup
	config_get disabled "$1" disabled
	[ "$disabled" = "1" ] && return
	config_get master "$1" master
	config_get backup "$1" backup
	if [ "$master" = "1" ] && [ -z "$_failback_master" ]; then
		_failback_master="$1"
		echo "master $1"
	fi
	if [ "$backup" = "1" ] && [ "$(omr_state_get openmptcprouter."$1".current)" = "1" ]; then
		echo "backup $1"
	fi
}

# For LuCI's server page, so that it doesn't need uci read access to
# openmptcprouter, which also holds the VPS passwords and API tokens
failback_status() {
	[ "$(omr_state_get openmptcprouter.omr.failback_hold)" = "1" ] || return 0
	echo "held"
	_failback_master=""
	config_load openmptcprouter
	config_foreach _failback_status_server server
}

start_interface() {
	[ -z "$1" ] && return
	_tracker_running "$1" && return
	# through procd, as "start <interface>": _launch_tracker only describes
	# the instance, which rc_procd then sends
	rc_procd start_service "$1"
}

start_service() {
	local ss_enable=0
	local ss_rust_enable=0
	local instance="$1"
	if [ -z "$instance" ]; then
		logger -t "omr-tracker" "Launching..."
		count=1111
		config_load shadowsocks-libev
		config_foreach _initialize_shadowsocks_tracker server
	
		count=1111
		config_load shadowsocks-rust
		config_foreach _initialize_shadowsocks_rust_tracker server
	
		# NOT gated on network.globals.multipath: _launch_tracker already
		# skips every interface whose own multipath is unset or "off", and
		# the tunnel (omrvpn/glorytun) is explicitly exempt from that filter.
		# Gating the whole loop on the global switch meant a router with MPTCP
		# globally disabled - set by hand, or pushed by a VPS whose API
		# answers mptcp.enabled=0 (see openmptcprouter-vps _get_config) -
		# launched no WAN tracker and, worse, no omrvpn tracker at all: nothing
		# installed the default route through the tunnel, no WAN ever failed
		# over, and every interface state/latency on the status page stayed
		# frozen, i.e. "MPTCP disabled -> nothing works". The multipath command
		# itself stays guarded where it is actually called (003-up,
		# 010-services), which is all commit b78f2340 needed to do.
		config_load network
		config_foreach _launch_tracker interface

		if [ "$ss_enable" = "1" ]; then
			config_load shadowsocks-libev
			config_foreach _launch_shadowsocks_tracker ss_local
		#elif [ "$(uci -q get shadowsocks-libev.sss0.disabled)" != "1" ]; then
		#	/etc/init.d/shadowsocks-libev rules_down
		fi
		if [ "$ss_rust_enable" = "1" ]; then
			config_load shadowsocks-rust
			config_foreach _launch_shadowsocks_rust_tracker ss_local
		fi
		config_load v2ray
		config_get v2rayenabled main enabled
		if [ "$v2rayenabled" = "1" ]; then
			_launch_v2ray_tracker
		fi

		config_load xray
		config_get xrayenabled main enabled
		if [ "$xrayenabled" = "1" ]; then
			_launch_xray_tracker
		fi
	
		# Launch the server tracker whenever at least one enabled server is
		# configured: besides master/backup failover it maintains the global
		# openmptcprouter.omr.server up/down state.
		haveserver=false
		config_load openmptcprouter
		config_foreach _have_server server
		[ "$haveserver" = true ] && _launch_server_tracker
		# omr.proxy / omr.server hold the last state written by their
		# tracker: when that tracker is not launched at all (proxy set to
		# "none", no enabled server configured), drop the key so consumers
		# never read a stale up/down value.
		[ "$(uci -q get openmptcprouter.settings.proxy)" = "none" ] && \
			omr_state_del openmptcprouter omr proxy
		[ "$haveserver" != true ] && {
			omr_state_del openmptcprouter omr server
			omr_state_del openmptcprouter omr failback_hold
		}
		# No commit: the keys above are runtime state (/var/state), and a
		# commit here only took other processes' pending openmptcprouter
		# changes along
		gretunnel=false
		config_load network
		config_foreach _gre_tunnel interface
		[ "$gretunnel" = true ] && _launch_gre_tracker
		logger -t "omr-tracker" "Launched"
	else
		_cleanup_locks "${instance}"
		config_load network
		_launch_tracker ${instance}
	fi
}

stop_service() {
	# Trackers are going down: any lock left behind would be stale.
	_cleanup_locks "$1"
	# Stopped for good, the service being disabled: no tracker comes back
	# before a reboot to clear a "down" (see _release_down). Not on a plain
	# stop, mostly half of a stop and start (wizard, proxy change, restart),
	# where putting a WAN in error back in MPTCP until the start only churns.
	if [ -z "$1" ] && [ -z "$_omr_restarting" ] && ! enabled; then
		config_load network
		config_foreach _untracked interface
	fi
}

service_triggers() {
	procd_add_reload_trigger omr-tracker network
}

reload_service() {
	start "$@"
}

# rc.common runs restart(), not restart_service(), for "restart": named
# restart, this replaces rc.common's
restart() {
	local _cg _kill_wait=0
	if [ -n "$1" ]; then
		# Run by the tracker itself (001-initialize), this script is in the
		# tracker's cgroup, which procd kills once the tracker has exited:
		# leave it, or the tracker is never started again
		read -r _cg 2>/dev/null </proc/self/cgroup
		case "$_cg" in
			*"/services/omr-tracker/$1") echo "$$" 2>/dev/null >/sys/fs/cgroup/cgroup.procs ;;
		esac
		procd_kill "omr-tracker" "$1" 2>/dev/null
		# Add the instance back once the tracker has exited, which procd's
		# KILL ensures term_timeout after the TERM: added back while it
		# stops, the procd of the 6.18 images kills it at once, with the
		# post-tracking hook it runs (Ysurac/openmptcprouter#4389)
		while _tracker_running "$1" && [ "$_kill_wait" -lt 35 ]; do
			sleep 1
			_kill_wait=$((_kill_wait + 1))
		done
		_cleanup_locks "$1"
		start "$@"
	else
		# Run by a tracker, as above: a stop kills what is left in its cgroup
		read -r _cg 2>/dev/null </proc/self/cgroup
		case "$_cg" in
			*"/services/omr-tracker/"*) echo "$$" 2>/dev/null >/sys/fs/cgroup/cgroup.procs ;;
		esac
		trap '' TERM
		_omr_restarting=1
		stop
		_omr_restarting=""
		trap - TERM
		# stop_service removed the locks before the trackers got the TERM, and
		# a tracker finishes the post-tracking hook it runs first: its 003-up
		# then removed, on exit, the lock of the 003-up of the new tracker
		while _any_tracker_running && [ "$_kill_wait" -lt 35 ]; do
			sleep 1
			_kill_wait=$((_kill_wait + 1))
		done
		_cleanup_locks
		start
	fi
}
