fix: auto-detect WAN roles — cellular vs fiber

- Detect Eyeride cellular interface by checking 192.168.10.x subnet
- Map correctly: ethernet/fiber → wan, Eyeride 5G → modem_0001
- Add carrier detection: AT&T for fiber gateways, Eyeride for cellular
- Fix sel_primary: fiber is active by default unless cellular route exists
- Fixes console showing 'on cellular' when actually on AT&T fiber

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 02:19:18 +00:00
parent 58ac96fd3c
commit 8dba04a1d0
@@ -293,6 +293,76 @@ _collect_wan() {
# ── Telemetry Collection ───────────────────────────────────────────────────
# Detect which interface is the Eyeride cellular link.
# The Eyeride runs on 192.168.10.x subnet; the interface with an IP in that
# range is the cellular path. The other WAN interface is the primary/fiber uplink.
# Returns: cellular_iface ethernet_iface
_detect_wan_roles() {
# Check if WAN2 is on the Eyeride subnet
_wan2_ip=$(ip addr show "$WAN2_IFACE" 2>/dev/null | grep 'inet ' | awk '{print $2}' | cut -d/ -f1)
if echo "$_wan2_ip" | grep -q '^192\.168\.10\.'; then
echo "$WAN2_IFACE $WAN1_IFACE" # WAN2 = cellular, WAN1 = ethernet
else
# Check if WAN1 is on the Eyeride subnet
_wan1_ip=$(ip addr show "$WAN1_IFACE" 2>/dev/null | grep 'inet ' | awk '{print $2}' | cut -d/ -f1)
if echo "$_wan1_ip" | grep -q '^192\.168\.10\.'; then
echo "$WAN1_IFACE $WAN2_IFACE" # WAN1 = cellular, WAN2 = ethernet
else
# Default: WAN2 is cellular (Eyeride typically on secondary port)
echo "$WAN2_IFACE $WAN1_IFACE"
fi
fi
}
# Derive carrier name for an interface. Uses reverse DNS on gateway or ISP detection.
# For the Eyeride (192.168.10.x), the carrier comes from the Eyeride's cellular operator.
_detect_carrier() {
_iface="$1"
_gw=$(ip route show default 2>/dev/null | grep "$_iface" | awk '{print $3}' | head -1)
# Check if this is the Eyeride network
_ip=$(ip addr show "$_iface" 2>/dev/null | grep 'inet ' | awk '{print $2}' | cut -d/ -f1)
if echo "$_ip" | grep -q '^192\.168\.10\.'; then
# This is the Eyeride — carrier comes from the Eyeride's cellular operator
_sig=$(_eyeride_signal 2>/dev/null)
_sig_carrier=$(echo "$_sig" | awk '{print $5}')
if [ -n "$_sig_carrier" ] && [ "$_sig_carrier" != "null" ] && [ "$_sig_carrier" != "Eyeride" ]; then
echo "$_sig_carrier"; return
fi
echo "Eyeride"; return
fi
# For the primary/ethernet interface, try to detect from gateway
if [ -n "$_gw" ]; then
# Common AT&T gateways
if echo "$_gw" | grep -qE '^192\.168\.[01]\.'; then
echo "AT&T"; return
fi
# Common Spectrum/Charter
if echo "$_gw" | grep -qE '^192\.168\.[0-9]+\.'; then
_rev=$(nslookup "$_gw" 2>/dev/null | grep -i name | head -1 | sed 's/.*= //')
if echo "$_rev" | grep -qi 'att\|sbcglobal\|bellsouth'; then
echo "AT&T"; return
fi
fi
fi
# Fallback
echo ""
}
# Add carrier and identity fields to a WAN JSON object.
# Usage: _add_wan_identity <wan_json> <iface>
_add_wan_identity() {
_json="$1" _iface="$2"
_carrier=$(_detect_carrier "$_iface")
_esc_carrier=$(_json_str "${_carrier}")
# Strip closing brace, add carrier, re-close
printf '%s' "$_json" | sed 's/}$//'
if [ -n "$_carrier" ]; then
printf ',"carrier":"%s"}' "${_esc_carrier}"
else
printf '}'
fi
}
# Build the full telemetry JSON payload (canonical GL format).
# Emits JSON to stdout; never fails — uses defaults for missing fields.
telemetry_collect() {
@@ -309,42 +379,69 @@ telemetry_collect() {
gps_fix=$(echo "$gps_out" | awk '{print $3}')
fi
# WAN1 (primary uplink, typically Ethernet → modem_0001 in canonical format)
wan1_json=$(_collect_wan "$WAN1_IFACE")
# Auto-detect which interface is cellular vs ethernet/fiber
_cell_iface="" _eth_iface=""
if _roles=$(_detect_wan_roles 2>/dev/null); then
_cell_iface=$(echo "$_roles" | awk '{print $1}')
_eth_iface=$(echo "$_roles" | awk '{print $2}')
else
_cell_iface="$WAN2_IFACE"
_eth_iface="$WAN1_IFACE"
fi
# WAN2 (Eyeride cellular → wan in canonical format)
wan2_json=$(_collect_wan "$WAN2_IFACE")
# Collect metrics for both WANs
_eth_json=$(_collect_wan "$_eth_iface")
_cell_json=$(_collect_wan "$_cell_iface")
# Eyeride cellular signal for WAN2 (try to enrich with rsrp/rsrq/sinr)
# Add carrier identity to each WAN
_eth_json=$(_add_wan_identity "$_eth_json" "$_eth_iface")
_cell_json=$(_add_wan_identity "$_cell_json" "$_cell_iface")
# Eyeride cellular signal for the cellular interface
eyeride_sig=$(_eyeride_signal 2>/dev/null)
_rsrp=$(echo "$eyeride_sig" | awk '{print $1}')
_rsrq=$(echo "$eyeride_sig" | awk '{print $2}')
_sinr=$(echo "$eyeride_sig" | awk '{print $3}')
_tech=$(echo "$eyeride_sig" | awk '{print $4}')
_carrier=$(echo "$eyeride_sig" | awk '{print $5}')
# Add cellular signal fields to the cellular WAN JSON
if echo "$_cell_json" | grep -q '"loss_pct"'; then
_cell_json=$(printf '%s' "$_cell_json" | sed 's/}$//')
if [ "$_rsrp" != "null" ] && [ -n "$_rsrp" ]; then
_esc_tech=$(_json_str "${_tech:-unknown}")
_cell_json="${_cell_json},\"rsrp_dbm\":${_rsrp},\"rsrq_db\":${_rsrq},\"sinr_db\":${_sinr},\"technology\":\"${_esc_tech}\"}"
else
_cell_json="${_cell_json}}"
fi
fi
# Build WAN2 JSON with cellular signal if available
if [ "$_rsrp" != "null" ] && [ -n "$_rsrp" ]; then
# Replace closing brace with signal fields
wan2_json=$(printf '%s' "$wan2_json" | sed 's/}$//')
_esc_tech=$(_json_str "${_tech:-unknown}")
_esc_carrier=$(_json_str "${_carrier:-Eyeride}")
wan2_json="${wan2_json},\"rsrp_dbm\":${_rsrp},\"rsrq_db\":${_rsrq},\"sinr_db\":${_sinr},\"technology\":\"${_esc_tech}\",\"carrier\":\"${_esc_carrier}\"}"
# WAN mapping in canonical GL format:
# modem_0001 = cellular (Eyeride 5G)
# wan = ethernet/fiber (AT&T Uverse)
_wan_modem="$WAN2_IFACE" # which physical iface maps to modem_0001
_wan_ether="$WAN1_IFACE" # which physical iface maps to wan
# Check if roles were swapped by auto-detection
if [ "$_cell_iface" != "$WAN2_IFACE" ]; then
# WAN1 is cellular, WAN2 is ethernet
_modem_json="$_cell_json"
_ether_json="$_eth_json"
else
# WAN2 is cellular, WAN1 is ethernet (normal)
_modem_json="$_cell_json"
_ether_json="$_eth_json"
fi
# Active WAN: determine from routing table (default route interface)
sel_primary="modem_0001"
sel_primary="wan" # default: fiber/ethernet is primary
default_iface=$(ip route show default 2>/dev/null | awk '{print $5}' | head -1)
if [ "$default_iface" = "$WAN2_IFACE" ]; then
sel_primary="wan"
if [ "$default_iface" = "$_cell_iface" ]; then
sel_primary="modem_0001" # cellular is the active path
fi
# Starlink — detect if present
sl_lat="null" sl_dl="null" sl_obs="0" sl_outage="0"
# Starlink is typically on a USB ethernet adapter or separate VLAN
if ip link show 2>/dev/null | grep -qi starlink; then
# Basic Starlink check — if interface exists, try to get metrics
sl_iface=$(ip link show 2>/dev/null | grep -i starlink | head -1 | awk -F': ' '{print $2}' | awk '{print $1}')
sl_iface=$(ip link show 2>/dev/null | grep -i starlink | head -1 | awk -F": " '{print $2}' | awk '{print $1}')
if [ -n "$sl_iface" ]; then
sl_ping=$(_ping_iface "$sl_iface")
sl_lat=$(echo "$sl_ping" | awk '{print $1}')
@@ -362,7 +459,7 @@ telemetry_collect() {
printf '{"device_id":"%s","timestamp":%d,"uptime":%d,"version":"%s","platform":"synology","sel_primary":"%s","active_wan":"%s","gps":{"lat":%s,"lon":%s,"fix":%s},"wan":{"modem_0001":%s,"wan":%s},"sim_slot":null,"starlink":{"latency_ms":%s,"dl_bps":%s,"obstructed":%s,"outage":%s},"state":%s}\n' \
"${esc_id}" "${now:-0}" "${uptime_s:-0}" "${esc_ver}" "${esc_sel}" "${esc_sel}" \
"${gps_lat}" "${gps_lon}" "${gps_fix}" \
"${wan1_json}" "${wan2_json}" \
"${_modem_json}" "${_ether_json}" \
"${sl_lat}" "${sl_dl}" "${sl_obs}" "${sl_outage}" \
"${state}"
}