#!/bin/bash
#
# Copyright (C) 2025 Yannick Chabanois for Stellar
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
# This script configure weight on all interfaces


scheduler="$(uci -q get network.globals.mptcp_scheduler)"

# bpf_weight_rr reads the same endpoint_weights map as bpf_weight -- it is the
# weighted round-robin over it -- but it was missing from this list, so with
# that scheduler selected nothing ever pushed network.<iface>.multipath_weight
# into the map. The Weight field on the Network page silently did nothing:
# every endpoint stayed at whatever the map happened to hold (neutral 100 on a
# fresh registration), and traffic split evenly regardless of what was set.
if [ -n "$OMR_TRACKER_INTERFACE" ] && [ -n "$OMR_TRACKER_DEVICE" ] && \
	{ [ "$scheduler" = "mptcp_bpf_weight.o" ] || [ "$scheduler" = "mptcp_bpf_burstweight.o" ] || [ "$scheduler" = "mptcp_bpf_weight_rr.o" ] || [ "$scheduler" = "mptcp_bpf_overflow.o" ] || \
	  [ "$scheduler" = "bpf_weight" ] || [ "$scheduler" = "bpf_burstweight" ] || [ "$scheduler" = "bpf_weight_rr" ] || [ "$scheduler" = "bpf_overflow" ]; } && \
	{ [ "$(uci -q get network."${OMR_TRACKER_INTERFACE}".multipath)" = "on" ] || [ "$(uci -q get network."${OMR_TRACKER_INTERFACE}".multipath)" = "master" ] || [ "$(uci -q get network."${OMR_TRACKER_INTERFACE}".multipath)" = "backup" ]; }; then
	# Unset is 100: the Weight field of the MPTCP page leaves it unset when
	# emptied, and the old weight stayed in the map until a reboot
	user_weight="$(uci -q get network."${OMR_TRACKER_INTERFACE}".multipath_weight)"
	# 1 to 256, as "ip route" takes it and LuCI asks: the schedulers read 0
	# as no weight (neutral 100) and add the weight to a signed counter
	case "$user_weight" in
		''|*[!0-9]*) user_weight="" ;;
		*)
			user_weight="${user_weight#"${user_weight%%[!0]*}"}"
			if [ -z "$user_weight" ]; then
				user_weight=1
			elif [ "${#user_weight}" -gt 3 ] || [ "$user_weight" -gt 256 ]; then
				user_weight=256
			fi
			;;
	esac
	weight="${user_weight:-100}"
	# omr-weight-sync keeps the VPS model's weight in /var/state, not in the
	# config: while it runs with decision weights on, the map holds that
	# weight, and putting the user's back here made the two alternate
	model_weight=""
	if [ "$(uci -q get omr-metrics.settings.enable_weight_sync)" != "0" ] && \
	   [ "$(uci -q get omr-metrics.settings.enable_decision_weights)" != "0" ] && \
	   [ -n "$(uci -q get omr-metrics.settings)" ]; then
		model_weight="$(uci -q -P /var/state get openmptcprouter."${OMR_TRACKER_INTERFACE}".decision_weight)"
		case "$model_weight" in
			''|*[!0-9]*) model_weight="" ;;
		esac
	fi
	# One line per key of the WAN, its IPv4 address and the ids of its IPv6
	# endpoints: comparing the first line only never set a weight the IPv6
	# keys lacked (an IPv6-only WAN, or the map new after an upgrade)
	current_weights="$(/usr/sbin/mptcp-scheduler-weight.sh show "${OMR_TRACKER_DEVICE}" 2>/dev/null | sed -n 's/.*weight=\([0-9]*\).*/\1/p')"
	if [ -n "$model_weight" ]; then
		# The config stays the user's: only the map follows the model
		if [ -z "$current_weights" ] || printf '%s\n' "$current_weights" | grep -qvx "$model_weight"; then
			/usr/sbin/mptcp-scheduler-weight.sh set "${OMR_TRACKER_DEVICE}" "$model_weight" >/dev/null 2>&1
		fi
	elif [ -z "$current_weights" ] || printf '%s\n' "$current_weights" | grep -qvx "$weight"; then
		/usr/sbin/mptcp-scheduler-weight.sh set "${OMR_TRACKER_DEVICE}" "$weight" >/dev/null 2>&1
		# The balancing route's nexthop weight follows, on a backup WAN
		# too, as omr-weight-sync sets it: deleting it there made the two
		# commit network to flash in turn. Committed only when it changed.
		# Only a weight the user set: .weight is also the Weight of the
		# Interfaces page (unset: 100 on the master, 1 on the others), and
		# an unset multipath_weight reset it to 100.
		if [ -n "$user_weight" ] && [ "$(uci -q get network."${OMR_TRACKER_INTERFACE}".weight)" != "$user_weight" ]; then
			uci -q set network."${OMR_TRACKER_INTERFACE}".weight="$user_weight"
			uci -q commit network
		fi
	fi
	# Keep the VPS's own weight_remote_id map in sync so the weighting
	# also holds for traffic the VPS sends (downloads) -- see
	# openmptcprouter-vps's _set_mptcp_weight_vps for details.
	# Backgrounded, and not gated on the local "did it change" check above:
	# the ids sent come from ip4table, and remote id 0 goes to the WAN the
	# route to the VPS uses, which a failover changes.
	# network.globals.mptcp_dscp_weight_vps_sync: unset/anything but "0"
	# means enabled (same shared toggle as 041-multipath-dscp) -- explicit
	# opt-out for anyone who doesn't want this HTTPS round trip / VPS-side
	# state at all.
	if [ "$(uci -q get network.globals.mptcp_dscp_weight_vps_sync)" != "0" ]; then
		# Only started when it has something to do, as in
		# 042-dscp-classify-vps-sync: getting to its cache through rc.common
		# and the init script took about 20 processes on every cycle of every
		# tracker. Something to do: what is sent for this WAN changed since
		# the last start (weight, ip4table, the routes to the VPSs, kept in
		# /var/state), a server has no cache (never synced, or rebooted), or
		# the retry of a failed push is due.
		. "${OMR_LIB_DIR:-/usr/share/omr/lib}/omr-state.sh"
		_weight_vps_due=""
		_weight_vps_now=$(date +%s)
		_weight_vps_sig="${model_weight:-$weight} $(uci -q get network."${OMR_TRACKER_INTERFACE}".ip4table)"
		_weight_vps_check() {
			local disabled username password port vpsip last wait _rest cache="/tmp/.mptcp_weight_vps_pins-$1"
			config_get disabled "$1" disabled
			config_get username "$1" username
			config_get password "$1" password
			config_get port "$1" port
			if [ "$disabled" = "1" ] || [ -z "$username" ] || [ -z "$password" ] || [ -z "$port" ]; then
				return
			fi
			config_get vpsip "$1" ip
			vpsip="${vpsip%% *}"
			[ -n "$vpsip" ] && _weight_vps_sig="$_weight_vps_sig $1:$(ip -4 route show "$vpsip" 2>/dev/null | tr -s ' \n' '  ')"
			if [ -f "${cache}.retry" ]; then
				read -r last wait _rest < "${cache}.retry"
				case "$last" in ''|*[!0-9]*) last=0 ;; esac
				case "$wait" in ''|*[!0-9]*) wait=0 ;; esac
				[ $((_weight_vps_now - last)) -ge "$wait" ] && _weight_vps_due=1
			elif [ ! -f "$cache" ]; then
				_weight_vps_due=1
			fi
		}
		config_load openmptcprouter
		config_foreach _weight_vps_check server
		if [ -n "$_weight_vps_due" ] || \
		   [ "$(omr_state_get "openmptcprouter.${OMR_TRACKER_INTERFACE}.weight_vps_sync")" != "$_weight_vps_sig" ]; then
			omr_state_set openmptcprouter "${OMR_TRACKER_INTERFACE}" weight_vps_sync "$_weight_vps_sig"
			( /etc/init.d/openmptcprouter-vps set_mptcp_weight_vps >/dev/null 2>&1 & )
		fi
	fi
fi

exit 0
