#!/bin/sh
# aurora-vpnsel (B22): chooses the LAN tunnel. Runs under supervise-daemon (/etc/init.d/aurora-vpnsel), after aurora-vpn (which brings up
# both wg0 = WireGuard via the free-turn relay on 127.0.0.1:9000 and wg1 = direct WireGuard to the server over LTE).
#   wg1 handshake fresh (age <= VS_DEAD s)  -> LAN default route (table 51820) via wg1, free-turn stopped (no VK/TURN traffic).
#   wg1 handshake stale                      -> free-turn started, LAN via wg0; wg1 keeps its keepalive, so it re-handshakes by itself
#                                               as soon as the direct path works again -> back to wg1 after VS_BACK fresh checks.
# A healthy wg1 re-handshakes every ~120 s (REKEY_AFTER_TIME, driven by the 25 s keepalive), so age > 150 s means the direct path is gone.
# State: /run/aurora-vpnsel/mode = direct | relay. Config: /etc/aurora/vpnsel.conf (optional).
# Operator whitelist probe (every VS_WL_PERIOD s, board traffic straight over LTE, TCP connect to :443, no data sent):
#   a domestic host answers and no foreign one does -> on | foreign answers -> off | nothing answers -> nodata.
#   /run/aurora-vpnsel/wl = "<on|off|nodata> <unix time>". Beeline in whitelist mode drops/RSTs every non-whitelisted (foreign) IP,
#   which also blocks the direct WireGuard server - so "on" explains a relay-only period.
VS_PERIOD=10 VS_DEAD=150 VS_BACK=2 VS_BOOT_WAIT=40 VS_WL_PERIOD=60
VS_WL_DOM="ya.ru vk.com gosuslugi.ru" VS_WL_FOR="1.1.1.1 8.8.8.8 9.9.9.9"
[ -f /etc/aurora/vpnsel.conf ] && . /etc/aurora/vpnsel.conf
TBL=51820; S=/run/aurora-vpnsel; mkdir -p $S; LOG=$S/vpnsel.log
now() { cut -d' ' -f1 /proc/uptime; }
log() { m="[$(now)] $*"; echo "$m" >> $LOG; echo "<5>[aurora-vpnsel] $*" > /dev/kmsg 2>/dev/null; }
age() { h=$(wg show $1 latest-handshakes 2>/dev/null | awk '{print $2; exit}'); [ -n "$h" ] && [ "$h" != 0 ] && echo $(( $(date +%s) - h )) || echo 99999; }
active() { ip route show table $TBL | awk '/^default/{print $3; exit}'; }
ft_on() { rc-service free-turn status >/dev/null 2>&1 || { rc-service free-turn start >/dev/null 2>&1; log "free-turn started"; }; }
ft_off() { rc-service free-turn status >/dev/null 2>&1 && { rc-service free-turn stop >/dev/null 2>&1; log "free-turn stopped"; }; }
wl_probe() {
	d=0; f=0
	for h in $VS_WL_DOM; do timeout 6 nc -z -w4 $h 443 >/dev/null 2>&1 && { d=1; break; }; done
	for h in $VS_WL_FOR; do timeout 6 nc -z -w4 $h 443 >/dev/null 2>&1 && { f=1; break; }; done
	if [ $f = 1 ]; then w=off; elif [ $d = 1 ]; then w=on; else w=nodata; fi
	o=$(cut -d' ' -f1 $S/wl 2>/dev/null)
	echo "$w $(date +%s)" > $S/wl
	[ "$w" != "$o" ] && log "whitelist ${o:-?} -> $w (domestic=$d foreign=$f)"
}
use() {   # use <wg0|wg1> <mode> <reason>
	[ -e /sys/class/net/$1 ] || return 1
	ip route replace default dev $1 table $TBL || return 1
	echo $2 > $S/mode; log "LAN -> $1 ($2): $3"
}

log "=== VPNSEL START dead=${VS_DEAD}s back=${VS_BACK}x${VS_PERIOD}s whitelist probe every ${VS_WL_PERIOD}s"
wl_probe
# wait for aurora-vpn to create the tunnels (it may still be starting at boot)
i=0; while ! { [ -e /sys/class/net/wg0 ] && [ -e /sys/class/net/wg1 ]; } && [ $i -lt 120 ]; do sleep 2; i=$((i+1)); done
[ -e /sys/class/net/wg1 ] || { log "no wg1 (no /etc/wireguard/wg1.conf?): relay only"; echo relay > $S/mode; ft_on
	while :; do sleep $VS_WL_PERIOD; wl_probe; done; }
# give the direct path a chance first: free-turn keeps running until wg1 proves itself
mode=$(cat $S/mode 2>/dev/null); fresh=0
if [ -z "$mode" ]; then
	i=0; while [ $(age wg1) -gt $VS_DEAD ] && [ $i -lt $VS_BOOT_WAIT ]; do sleep 1; i=$((i+1)); done
	if [ $(age wg1) -le $VS_DEAD ]; then use wg1 direct "handshake after ${i}s"; mode=direct; ft_off
	else use wg0 relay "no direct handshake in ${VS_BOOT_WAIT}s"; mode=relay; ft_on; fi
fi
n=0
while :; do
	sleep $VS_PERIOD
	n=$((n+1)); [ $((n * VS_PERIOD % VS_WL_PERIOD)) = 0 ] && wl_probe
	a1=$(age wg1)
	case "$mode" in
	direct)
		[ "$(active)" = wg1 ] || use wg1 direct "route was $(active), restored"
		if [ $a1 -gt $VS_DEAD ]; then
			ft_on; use wg0 relay "wg1 handshake age ${a1}s > ${VS_DEAD}s"; mode=relay; fresh=0
		fi;;
	*)
		[ "$(active)" = wg0 ] || use wg0 relay "route was $(active), restored"
		ft_on
		if [ $a1 -le $VS_DEAD ]; then fresh=$((fresh+1)); else fresh=0; fi
		if [ $fresh -ge $VS_BACK ]; then use wg1 direct "wg1 handshake age ${a1}s (fresh ${fresh}x)"; mode=direct; ft_off; fi;;
	esac
done
