Compare commits

..

5 Commits

Author SHA1 Message Date
kitadmin 30953399b6 fix: null gateway latency; skip aiwanbal generic modem_type for carrier detection
- Gateway ping latency is LAN RTT (<1ms), not WAN latency; set to null so
  dashboards don't show misleading sub-ms values for Eyeride-NAT'd interfaces
- aiwanbal reports modem_type=generic for wired ISP ports; treat same as none
  so carrier detection falls through to gateway IP pattern matching (AT&T etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 03:48:32 +00:00
kitadmin 088c256f58 fix: ping local gateway instead of curl when ICMP gives 100% loss
curl --interface fails for root on Synology due to policy routing. Pinging the
local gateway (e.g. Eyeride at 192.168.10.1) works reliably and confirms the
WAN path is up even when internet ICMP is NAT-blocked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 03:38:03 +00:00
kitadmin bbbc873bdb fix: HTTP fallback probe when ICMP reports 100% loss (Eyeride NAT blocks ICMP)
Eyeride 5G device passes HTTP traffic but drops ICMP, causing false 100% loss.
When ping gives 100% loss, try curl --interface to confirm connectivity; if
HTTP probe succeeds, correct loss to 0% and derive latency from connect time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 03:31:47 +00:00
kitadmin 8f2d6a7cbd fix: sanitize _mtime with tr to prevent ash arithmetic crash on Synology
stat output can include non-numeric chars on some RT2600ac firmware versions,
causing 0 to fail. Strip with tr -cd '0-9' before arithmetic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 03:29:10 +00:00
kitadmin a4ba9f8fcd fix: ash-compatible arithmetic in _cached_speedtest; clean up speedtest null guards
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 03:26:04 +00:00
@@ -207,6 +207,7 @@ _detect_wan_roles() {
_aiwanbal_available() { _aiwanbal_available() {
[ -d "$AIWANBAL_DIR" ] || return 1 [ -d "$AIWANBAL_DIR" ] || return 1
_mtime=$(stat -c %Y "${AIWANBAL_DIR}/wan1_score_avg" 2>/dev/null || stat -f %m "${AIWANBAL_DIR}/wan1_score_avg" 2>/dev/null) _mtime=$(stat -c %Y "${AIWANBAL_DIR}/wan1_score_avg" 2>/dev/null || stat -f %m "${AIWANBAL_DIR}/wan1_score_avg" 2>/dev/null)
_mtime=$(printf '%s' "$_mtime" | tr -cd '0-9')
[ -n "$_mtime" ] || return 1 [ -n "$_mtime" ] || return 1
_now=$(date +%s) _now=$(date +%s)
_age=$(( _now - _mtime )) _age=$(( _now - _mtime ))
@@ -238,6 +239,20 @@ _ping_iface() {
# Extract loss # Extract loss
_loss=$(echo "$_out" | sed -n 's/.* \([0-9]*\)% packet loss.*/\1/p') _loss=$(echo "$_out" | sed -n 's/.* \([0-9]*\)% packet loss.*/\1/p')
[ -z "$_loss" ] && _loss="null" [ -z "$_loss" ] && _loss="null"
# If ICMP to internet gives 100% loss, ping the local gateway —
# Eyeride NAT drops internet ICMP but the gateway (192.168.10.1) is reachable locally.
if [ "$_loss" = "100" ]; then
_gw=$(ip route show 2>/dev/null | grep "dev $_iface" | grep -v "^default" | awk '/via/{print $3; exit} /kernel/{print ""}' | head -1)
[ -z "$_gw" ] && _gw=$(ip route show default 2>/dev/null | grep "dev $_iface" | awk '{print $3}' | head -1)
if [ -n "$_gw" ]; then
_gw_out=$(ping -c 3 -W 2 "$_gw" 2>/dev/null)
_gw_loss=$(echo "$_gw_out" | sed -n 's/.* \([0-9]*\)% packet loss.*/\1/p')
if [ -n "$_gw_loss" ] && [ "$_gw_loss" != "100" ]; then
_loss="$_gw_loss"
_avg="null" # gateway RTT is LAN latency, not meaningful as WAN metric
fi
fi
fi
printf '%s %s %s\n' "${_avg}" "${_jitter}" "${_loss}" | tr -d '\n\r' printf '%s %s %s\n' "${_avg}" "${_jitter}" "${_loss}" | tr -d '\n\r'
} }
@@ -292,8 +307,8 @@ _collect_wan() {
_dl=$(echo "$_st" | awk '{print $1}') _dl=$(echo "$_st" | awk '{print $1}')
_ul=$(echo "$_st" | awk '{print $2}') _ul=$(echo "$_st" | awk '{print $2}')
fi fi
[ -z "$_dl" ] || [ "$_dl" = "0" ] && _dl="0.0" [ -n "$_dl" ] || _dl="0.0"
[ -z "$_ul" ] || [ "$_ul" = "0" ] && _ul="0.0" [ -n "$_ul" ] || _ul="0.0"
fi fi
printf '{"score":%s,"latency_ms":%s,"dl_mbps":%s,"ul_mbps":%s,"jitter_ms":%s,"loss_pct":%s}' \ printf '{"score":%s,"latency_ms":%s,"dl_mbps":%s,"ul_mbps":%s,"jitter_ms":%s,"loss_pct":%s}' \
@@ -319,7 +334,7 @@ _detect_carrier() {
for _n in 1 2; do for _n in 1 2; do
_mod=$(_read_file "${AIWANBAL_DIR}/wan${_n}_modem_type" 2>/dev/null) _mod=$(_read_file "${AIWANBAL_DIR}/wan${_n}_modem_type" 2>/dev/null)
_active=$(_read_file "${AIWANBAL_DIR}/wan${_n}_state" 2>/dev/null) _active=$(_read_file "${AIWANBAL_DIR}/wan${_n}_state" 2>/dev/null)
if [ "$_mod" != "eyeride" ] && [ "$_mod" != "none" ] && [ -n "$_mod" ] && [ "$_active" = "up" ]; then if [ "$_mod" != "eyeride" ] && [ "$_mod" != "none" ] && [ "$_mod" != "generic" ] && [ -n "$_mod" ] && [ "$_active" = "up" ]; then
echo "$_mod"; return echo "$_mod"; return
fi fi
done done
@@ -415,8 +430,10 @@ _cached_speedtest() {
_f="${STATE_DIR}/speedtest-result-${_iface}" _f="${STATE_DIR}/speedtest-result-${_iface}"
[ -f "$_f" ] || return 1 [ -f "$_f" ] || return 1
_mtime=$(stat -c %Y "$_f" 2>/dev/null || stat -f %m "$_f" 2>/dev/null) _mtime=$(stat -c %Y "$_f" 2>/dev/null || stat -f %m "$_f" 2>/dev/null)
_mtime=$(printf '%s' "$_mtime" | tr -cd '0-9')
[ -n "$_mtime" ] || return 1 [ -n "$_mtime" ] || return 1
_age=$(( $(date +%s) - _mtime )) _st_now=$(date +%s)
_age=$(( _st_now - _mtime ))
[ "$_age" -lt 2100 ] || return 1 [ "$_age" -lt 2100 ] || return 1
cat "$_f" cat "$_f"
} }