#!/bin/sh
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2025-2026 Chester A. Unal <chester.a.unal@arinc9.com>

TIMEOUT_MS=900
COUNT=3
HOST_ADDR="1.1.1.1 8.8.4.4"
REACHABLE_HOST=1
INTERVAL=1
PERIOD_MS=10
WINDOW_SIZE=100
LOGFILE="/var/log/bsbf-mptcp.log"
SUBFLOW_LIMIT=8
# Check if uci is available.
command -v uci >/dev/null 2>&1 && UCI=1

# Adjust MPTCP subflow limit.
ip mp limits set subflows "$SUBFLOW_LIMIT"

while true; do
	sleep "$INTERVAL"

	# Get all default routes with a metric.
	routes=$(ip route show | grep "^default" | grep "metric")

	# Continue if there are no routes.
	[ -z "$routes" ] && continue

	# Extract interfaces from default routes.
	interfaces=$(echo "$routes" | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}')

	list=""
	data=""
	for IFACE in $interfaces; do
		# If interface is declared backup and its endpoint is nobackup,
		# set it backup and continue for declared backup interface.
		ENDPOINTS=$(ip mp e)
		if [ -n "$UCI" ] && uci -q get bsbf-mptcp.main.subflow_backup | grep -q "$IFACE"; then
			if echo "$ENDPOINTS" | grep -q "dev $IFACE" && \
			   ! echo "$ENDPOINTS" | grep "dev $IFACE" | grep -q "backup"; then
				bsbf-mptcp-helper backup "$IFACE"
			fi
			continue
		fi

		# Run fping to get the average RTT of an interface.
		OUTPUT=$(fping -q -s -p "$PERIOD_MS" -t "$TIMEOUT_MS" -c "$COUNT" -I "$IFACE" -x "$REACHABLE_HOST" $HOST_ADDR 2>&1)
		# Remove the interface if there's no connectivity and continue.
		[ "$?" -ne 0 ] && { bsbf-mptcp-helper remove "$IFACE" ; continue; }

		# Set isbackup if interface is a backup subflow. Otherwise, add
		# an mptcp endpoint for the interface if there isn't one for it.
		isbackup=0
		if echo "$ENDPOINTS" | grep "dev $IFACE" | grep -q "backup"; then
			isbackup=1
		elif ! echo "$ENDPOINTS" | grep -q "dev $IFACE"; then
			bsbf-mptcp-helper add "$IFACE"
		fi

		list=""$list"LIST $IFACE\n"
		rtt=$(echo "$OUTPUT" | grep "avg round trip time" | awk '{print $1}')
		data=""$data"DATA $IFACE $rtt $isbackup\n"
	done

	# If there is at least one interface, pipe output to awk.
	[ -n "$list" ] && printf "%b\n" "$list\nDATASTART\n$data"
done | awk -v w="$WINDOW_SIZE" -v logfile="$LOGFILE" '
$1 == "LIST" {
	# Store the reported interfaces.
	reported[$2] = 1
}
$1 == "DATASTART" {
	# Remove the interfaces that were not reported from comparable.
	for (i in comparable) {
		if (!reported[i])
			delete comparable[i]
	}
	delete reported

	# Separate every data group log.
	print "---" >> logfile
}
$1 == "DATA" {
	iface = $2; rtt = $3; isbackup = $4
	ts = strftime("%Y-%m-%d %H:%M:%S", systime(), 1)

	# Set entry position for the interface. Use the window size amount for
	# the entry size. Entry position starts at 1. The position will be reset
	# to 1 if the biggest window size entry position is reached.
	idx[iface] = (idx[iface] % w) + 1

	# Subtract the historic RTT at the current position from the summation.
	sum[iface] -= hist[iface, idx[iface]]

	# Overwrite the historic RTT at the current position with the current
	# RTT.
	hist[iface, idx[iface]] = rtt

	# Add the current RTT to the summation.
	sum[iface] += rtt

	# If the current amount of entries are less than the window size amount,
	# increment the entry count by one.
	if (count[iface] < w)
		count[iface]++

	# If the final amount of entries are less than the window size amount,
	# conclude processing this line.
	if (count[iface] < w) {
		print ts, "Collecting data for", iface ". Current count:", count[iface] >> logfile
		fflush(logfile)
		next
	}

	# Calculate the average RTT of all entries of the current interface.
	avg = sum[iface] / count[iface]

	print ts, iface, "avg:", sprintf("%.2f", avg), "| backup:", isbackup >> logfile
	fflush(logfile)

	# Declare the interface comparable.
	comparable[iface] = 1

	# If there are less than two comparable interfaces, conclude processing
	# this line.
	if (length(comparable) < 2)
		next

	# Iterate over all comparable interfaces to find the best one,
	# which is the one with the lowest average RTT. Reset best_avg
	# to 0 so the first comparable interface always becomes the
	# initial best, then replace it if a lower average is found.
	best_avg = 0
	for (i in comparable) {
		i_avg = sum[i] / count[i]
		if (best_avg == 0 || i_avg < best_avg) {
			best_avg = i_avg
			best_iface = i
		}
	}

	# Decide if the current interface is in the same RTT group as the best.
	if (best_avg < 80)
		same_group = (avg < 80)
	else if (best_avg < 160)
		same_group = (avg >= 80 && avg < 160)
	else if (best_avg < 480)
		same_group = (avg >= 160 && avg < 480)
	else
		same_group = (avg >= 480)

	print ts, "best:", best_iface, "|", iface, "on same RTT group as best:", same_group >> logfile

	if (!same_group && !isbackup) {
		print ts, "Making", iface, "backup." >> logfile
		system("bsbf-mptcp-helper backup " iface)
	} else if (same_group && isbackup) {
		print ts, "Making", iface, "nobackup." >> logfile
		system("bsbf-mptcp-helper nobackup " iface)
	}

	fflush(logfile)
}
'
