#!/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 = " ". 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 [ -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