#!/bin/sh # smartwan-adapter.sh — Read SmartWAN state and write dw_weight_ratio. # NEVER calls loadbalance lb-enable. NEVER calls loadbalance at all. # Rides entirely on top of SmartWAN — does not own routing. # # Public API: # sw_read_state read SmartWAN mode, interfaces, current weights # sw_read_wan_state read per-WAN state: operstate, gateway, weight # sw_write_weights write dw_weight_ratio (50=balanced, 20/80=biased) # sw_signal_reload touch SmartWAN to re-read config (light touch, no conntrack flush) # # Config file: /usr/syno/etc/smartwan/smartwan.conf # Weight field: dw_weight_ratio = <0-100> (WAN1 share %, WAN2 = 100-WAN1) # - dw_weight_ratio appears twice in the conf (once for each ifname section) # - We update BOTH occurrences # - 50/50 = balanced, 70 = 70% WAN1 / 30% WAN2 SMARTWAN_CONF="/usr/syno/etc/smartwan/smartwan.conf" STATE_DIR="${STATE_DIR:-/tmp/syno-balance}" LOG_TAG="${LOG_TAG:-syno-balance}" log() { logger -t "$LOG_TAG" -p local0.warn "$*"; } warn() { logger -t "$LOG_TAG" -p local0.warn "WARNING: $*"; } # ── Read SmartWAN global state ────────────────────────────────────── sw_read_state() { [ -f "$SMARTWAN_CONF" ] || { warn "smartwan.conf not found"; return 1; } . "$SMARTWAN_CONF" 2>/dev/null echo "MODE=${smartwan_mode:-unknown}" echo "WAN1_IFNAME=${smartwan_ifname_1:-wan}" echo "WAN2_IFNAME=${smartwan_ifname_2:-lan1}" echo "WAN1_WEIGHT=${dw_weight_ratio:-50}" } # ── Read a single WAN's runtime state ─────────────────────────────── # Returns: STATE=up|down GW=x.x.x.x WEIGHT=N sw_read_wan_state() { local iface="$1" [ -n "$iface" ] || return 1 # Physical link state local operstate="down" operstate=$(cat "/sys/class/net/${iface}/operstate" 2>/dev/null || echo "unknown") # Gateway from routing table local gw="" gw=$(ip route show dev "$iface" 2>/dev/null | grep default | awk '{print $3}' | head -1) [ -z "$gw" ] && gw=$(ip route show dev "$iface" 2>/dev/null | awk '/via/ {print $3}' | head -1) # Current weight from smartwan.conf local weight=0 [ -f "$SMARTWAN_CONF" ] && . "$SMARTWAN_CONF" 2>/dev/null weight="${dw_weight_ratio:-50}" echo "IFACE=${iface} STATE=${operstate} GW=${gw:-none} WEIGHT=${weight}" } # ── Write dw_weight_ratio for both WANs ───────────────────────────── # $1: WAN1 weight (0-100, WAN2 = 100-WAN1) # Sets dw_weight_ratio in smartwan.conf for both ifname sections. # Does NOT call loadbalance. SmartWAN re-reads on its own cycle. sw_write_weights() { local w1="$1" case "$w1" in ''|*[!0-9]*) warn "invalid weight: $w1"; return 1 ;; esac [ "$w1" -lt 0 ] && w1=0 [ "$w1" -gt 100 ] && w1=100 local current=50 [ -f "$SMARTWAN_CONF" ] && . "$SMARTWAN_CONF" 2>/dev/null current="${dw_weight_ratio:-50}" # Only write if changed by >= 10 (hysteresis via lib-decide.sh) local diff=$(( w1 > current ? w1 - current : current - w1 )) if [ "$diff" -lt 10 ]; then return 0 # within hysteresis band — no-op fi # Write to smartwan.conf using sed — safe, atomic-ish # SmartWAN has dw_weight_ratio on both ifname sections if [ -f "$SMARTWAN_CONF" ]; then sed -i "s/^dw_weight_ratio=.*/dw_weight_ratio=${w1}/" "$SMARTWAN_CONF" 2>/dev/null fi log "smartwan: dw_weight_ratio ${current} -> ${w1} (WAN1=${w1}% WAN2=$((100-w1))%)" mkdir -p "$STATE_DIR" echo "$w1" > "$STATE_DIR/sw_weight_written" echo "$(date +%s)" > "$STATE_DIR/sw_weight_time" } # ── Light touch — signal SmartWAN to re-read config ──────────────── # Uses synoservice if available. NEVER calls loadbalance lb-enable. sw_signal_reload() { # SRM 1.3+ has synoservice to restart individual services if command -v synoservice >/dev/null 2>&1; then synoservice --restart smartwan 2>/dev/null && \ { log "smartwan: light-reload via synoservice"; return 0; } fi # Fallback: just touch the config — SmartWAN polls it touch "$SMARTWAN_CONF" 2>/dev/null log "smartwan: config touched for polling re-read" return 0 } # ── Check if SmartWAN is in load-balance mode ─────────────────────── sw_is_lb_mode() { [ -f "$SMARTWAN_CONF" ] || return 1 local mode="" mode=$(grep '^smartwan_mode=' "$SMARTWAN_CONF" 2>/dev/null | cut -d= -f2) case "$mode" in loadbalance|loadbalancing_failover) return 0 ;; *) return 1 ;; esac }