#!/bin/sh # Haelt den WireGuard-Tunnel "fritzbox" auf dem besseren Uplink. # Regel (seit 09.10.2026): Landal (wwan2), solange es schneller ist; Huawei (wwan), # wenn Landal langsamer als der Huawei ist oder kein Internet hat. Faellt einer aus, # laeuft der Tunnel ueber den anderen. # Der gesamte LAN-Verkehr laeuft durch den Tunnel, deshalb genuegt es, die Route # zum Tunnel-Endpunkt umzulegen. # Zustand: cat /tmp/uplink-watchdog.state Log: logread -e uplink-watchdog . /lib/functions/network.sh WG=fritzbox HUAWEI=wwan LANDAL=wwan2 TARGETS="1.1.1.1 8.8.8.8 9.9.9.9" INTERVAL=10 DOWN_AFTER=3 # so viele Fehlversuche in Folge, dann gilt ein Uplink als gestoert UP_AFTER=6 # so viele Erfolge in Folge, dann gilt er wieder als in Ordnung RESOLVE_EVERY=30 STATE=/tmp/uplink-watchdog.state # Vorgabe aus Home Assistant (auto|huawei|landal), geschrieben von /www/cgi-bin/uplink-mode. # Fehlt die Datei (z. B. nach einem Neustart), gilt auto. MODEFILE=/tmp/uplink-mode # Home Assistant Arcen bekommt den aktuellen Uplink gemeldet (Automation "System: Router-Uplink uebernehmen") HA_HOOK=http://10.10.11.12/api/webhook/router-uplink- # Geschwindigkeitsvergleich (braucht das Paket curl; ohne curl bleibt es bei Landal, solange es Internet hat) SPEED_HOST="http://speed.cloudflare.com/__down?bytes=" SPEED_EVERY=90 # alle 90 Durchlaeufe = 15 Minuten PROBE_BYTES=1000000 # Landal allein: 1 MB (kostet kein Huawei-Volumen) CMP_BYTES=300000 # Vergleich beider Leitungen: je 300 kB FAST_KBS=1000 # ist Landal schneller als 1000 kB/s (8 Mbit/s), wird der Huawei nur HUAWEI_EVERY=4 # bei jeder 4. Messung (stuendlich) mitgemessen, das spart Volumen: # ca. 7 MB pro Tag, sonst (Landal langsam) ca. 29 MB pro Tag EP_HOST=$(uci -q get network.@wireguard_$WG[0].endpoint_host) EP_PORT=$(uci -q get network.@wireguard_$WG[0].endpoint_port) log() { logger -t uplink-watchdog "$*"; } check() { local t for t in $TARGETS; do ping -c 1 -W 2 -I "$1" "$t" >/dev/null 2>&1 && return 0 done return 1 } # speed : Download-Geschwindigkeit in kB/s direkt ueber dieses Geraet (am Tunnel vorbei) speed() { local s command -v curl >/dev/null || return 1 s=$(curl -s -m 10 --interface "$1" -o /dev/null -w '%{speed_download}' "$SPEED_HOST$2" 2>/dev/null) s=${s%%.*} case "$s" in ''|*[!0-9]*) return 1 ;; esac echo $((s / 1000)) } # track : Erreichbarkeit eines Uplinks mit Entprellung fuehren track() { local up fails oks eval "up=\$${1}_up; fails=\$${1}_fails; oks=\$${1}_oks" if [ -n "$2" ] && [ -n "$3" ] && check "$2"; then fails=0 oks=$((oks + 1)) if [ $up = 0 ] && [ $oks -ge $UP_AFTER ]; then up=1; log "$4 hat wieder Internet"; fi else oks=0 fails=$((fails + 1)) if [ $up = 1 ] && [ $fails -ge $DOWN_AFTER ]; then up=0; log "$4 ohne Internet"; fi fi eval "${1}_up=$up; ${1}_fails=$fails; ${1}_oks=$oks" } h_up=1; h_fails=0; h_oks=0 l_up=1; l_fails=0; l_oks=0 pref=landal sp=0 hkbs="" lkbs="" sent="" cur="" mode=auto ep_ip="" n=0 # Ergebnis des Knopfs "Uplinks jetzt testen" (geschrieben von /usr/sbin/uplink-test-run) TESTFILE=/tmp/uplink-test.pref TEST_HOLD=1800 # so lange (s) nach einem Test macht der Router keine eigene Messung testnote="" testtime=0 while :; do hdev=""; hgw=""; ldev=""; lgw="" network_flush_cache network_get_device hdev $HUAWEI network_get_gateway hgw $HUAWEI network_get_device ldev $LANDAL network_get_gateway lgw $LANDAL track h "$hdev" "$hgw" Huawei track l "$ldev" "$lgw" Landal # Ergebnis eines von Hand gestarteten Tests uebernehmen if [ -r $TESTFILE ]; then T_PREF=""; T_H=""; T_L=""; T_NOTE="" . $TESTFILE rm -f $TESTFILE if [ -n "$T_PREF" ]; then pref=$T_PREF [ -n "$T_H" ] && hkbs=$T_H [ -n "$T_L" ] && lkbs=$T_L testnote=$T_NOTE; testtime=$(date +%s) log "Test: bevorzugt jetzt $pref (Landal ${lkbs:-?} kB/s, Huawei ${hkbs:-?} kB/s)" fi fi # Geschwindigkeit: Landal jedes Mal (kostenlos), Huawei stuendlich oder wenn Landal nicht klar schnell ist if [ $((n % SPEED_EVERY)) = 3 ] && [ $(( $(date +%s) - testtime )) -ge $TEST_HOLD ] && command -v curl >/dev/null; then was=$pref sp=$((sp + 1)) l=""; h="" if [ $l_up = 1 ] && [ -n "$lgw" ]; then l=$(speed "$ldev" $PROBE_BYTES) || l=0; fi if [ $h_up = 1 ] && [ -n "$hgw" ]; then if [ -z "$l" ] || [ "$l" -lt $FAST_KBS ] || [ -z "$hkbs" ] || [ $((sp % HUAWEI_EVERY)) = 0 ]; then h=$(speed "$hdev" $CMP_BYTES) || h=0 fi fi if [ -n "$l" ] && [ -n "$h" ]; then # knapper Fall: Landal mit derselben Dateigroesse messen wie den Huawei if [ "$l" -lt $FAST_KBS ] && l2=$(speed "$ldev" $CMP_BYTES); then l=$l2; fi if [ $pref = landal ]; then # erst wechseln, wenn der Huawei deutlich (20 %) schneller ist [ "$h" -gt $((l * 12 / 10)) ] && pref=huawei else [ "$l" -gt "$h" ] && pref=landal fi fi [ -n "$l" ] && lkbs=$l [ -n "$h" ] && hkbs=$h [ $pref != $was ] && log "Bevorzugt jetzt $pref (Landal ${lkbs:-?} kB/s, Huawei ${hkbs:-?} kB/s)" fi # Vorgabe aus Home Assistant: fester Uplink oder auto. Auch bei fester Vorgabe gilt: # hat der gewaehlte Uplink kein Internet, laeuft der Tunnel ueber den anderen. m=auto [ -r $MODEFILE ] && read m < $MODEFILE case "$m" in huawei|landal) ;; *) m=auto ;; esac if [ "$m" != "$mode" ]; then log "Vorgabe jetzt: $m"; mode=$m; fi case $mode in huawei) order="h l" ;; landal) order="l h" ;; *) if [ $pref = landal ]; then order="l h"; else order="h l"; fi ;; esac use="" for x in $order; do eval "xup=\$${x}_up; xdev=\$${x}dev; xgw=\$${x}gw" if [ $xup = 1 ] && [ -n "$xgw" ]; then use=$x; dev=$xdev; gw=$xgw; break; fi done if [ -z "$use" ]; then # keiner gilt als in Ordnung: nimm den ersten, der ueberhaupt verbunden ist for x in $order; do eval "xdev=\$${x}dev; xgw=\$${x}gw" if [ -n "$xgw" ]; then use=$x; dev=$xdev; gw=$xgw; break; fi done fi peer=$(wg show $WG peers 2>/dev/null | head -n 1) # Endpunkt-Adresse der FRITZ!Box (MyFRITZ) regelmaessig neu aufloesen if [ -z "$ep_ip" ] || [ $((n % RESOLVE_EVERY)) = 0 ]; then new=$(resolveip -4 -t 5 "$EP_HOST" 2>/dev/null | head -n 1) [ -z "$new" ] && [ -z "$ep_ip" ] && new=$(wg show $WG endpoints 2>/dev/null | head -n 1 | cut -f 2 | cut -d : -f 1) if [ -n "$new" ] && [ "$new" != "$ep_ip" ]; then [ -n "$ep_ip" ] && ip route del "$ep_ip/32" 2>/dev/null ep_ip=$new [ -n "$peer" ] && wg set $WG peer "$peer" endpoint "$ep_ip:$EP_PORT" log "Tunnel-Endpunkt ist $ep_ip" fi fi n=$((n + 1)) if [ -n "$use" ] && [ -n "$ep_ip" ]; then case "$(ip -4 route show "$ep_ip/32" | head -n 1)" in *"via $gw dev $dev"*) ;; *) ip route replace "$ep_ip/32" via "$gw" dev "$dev" # alte NAT-Zuordnung des Tunnels verwerfen, sonst geht er mit falscher Absenderadresse raus echo "$ep_ip" > /proc/net/nf_conntrack 2>/dev/null ;; esac if [ "$use" != "$cur" ]; then log "Tunnel laeuft jetzt ueber $([ $use = h ] && echo Huawei || echo Landal) ($dev, Gateway $gw)" cur=$use fi fi case "$cur" in l) label=Landal if [ $mode = landal ]; then grund="Manuell eingestellt" elif [ $mode = auto ] && [ $pref = landal ]; then grund="Landal schneller" elif [ $h_up = 0 ]; then grund="Huawei ohne Internet" else grund="Huawei nicht verbunden" fi ;; h) label=Huawei if [ $mode = huawei ]; then grund="Manuell eingestellt" elif [ $mode = auto ] && [ $pref = huawei ]; then grund="Landal langsamer" elif [ -z "$lgw" ]; then grund="Landal nicht verbunden" else grund="Landal ohne Internet" fi ;; *) label="Kein Uplink" grund="Router hat keine Verbindung" ;; esac if [ -n "$testnote" ]; then if [ $(( $(date +%s) - testtime )) -lt $TEST_HOLD ]; then grund="$grund ($testnote)"; else testnote=""; fi fi hs=$(wg show $WG latest-handshakes 2>/dev/null | head -n 1 | cut -f 2) now=$(date +%s) { echo "uplink=$label" echo "vorgabe=$mode" echo "bevorzugt=$pref" echo "huawei_internet=$h_up" echo "landal_internet=$l_up" echo "huawei_kbs=${hkbs:--}" echo "landal_kbs=${lkbs:--}" echo "grund=$grund" echo "endpunkt=$ep_ip" echo "handshake_vor_s=$([ -n "$hs" ] && [ "$hs" != 0 ] && echo $((now - hs)) || echo -)" echo "geprueft=$(date '+%d.%m.%Y %H:%M:%S')" } > $STATE.tmp && mv $STATE.tmp $STATE # bei Aenderung sofort an Home Assistant melden, sonst alle 5 Minuten zur Auffrischung msg="$label|$grund|$hkbs|$lkbs" if [ "$msg" != "$sent" ] || [ $((n % RESOLVE_EVERY)) = 0 ]; then wget -q -T 5 -O /dev/null --post-data="uplink=$(echo "$label" | tr ' ' '+')&grund=$(echo "$grund" | tr ' ' '+')&huawei=$hkbs&landal=$lkbs" "$HA_HOOK" 2>/dev/null && sent=$msg fi sleep $INTERVAL done