Files
openwrt-build-helper/reports/2026-10-28-linksys-mx8500-wwan-recovery.md

284 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Linksys MX8500:Wi-Fi 上联 wwan2 / wwan5 断流后无法自动恢复
- 日期:2026-10-28
- 设备:Linksys MX8500(`linksys-mx8500.tail694d4.ts.net`)
- 系统:OpenWrt 25.12.5 r33051-f5dae5ece4,qualcommax/ipq807x
- 无线固件:ath11k WLAN.HK.2.9.0.1-02146
## 背景
路由器没有有线 WAN,用两个 STA 接口中继上游 Wi-Fi,mwan3 做负载均衡:
| 接口 | 设备 | 射频 | 上游 SSID | MAC | 说明 |
|---|---|---|---|---|---|
| wwan5 | `phy0-sta0` | radio0(5G) | Urban Residence | 固定 | 上游要求登录,按 MAC 记录 |
| wwan2 | `phy1-sta0` | radio1(2.4G) | Urban Resident | random | 开放网络,无需登录 |
两个射频上同时跑着本地热点(AP + STA 共用一个射频)。两个上游 SSID 后面是同一个 Nomadix 网关(`00:50:e8:04:90:8c`)。
现象:wwan2 / wwan5 断流后会一直不通,要手动重启 Wi-Fi 才恢复。其他设备连同样的 SSID 没有问题。
## 排查结果
### 故障时的状态(wwan2)
| 项目 | 结果 |
|---|---|
| `iw link` | 关联正常,信号 -53 dBm,beacon 正常,无 beacon loss |
| 下行速率 | 162 Mbit/s,正常 |
| 上行 | **tx failed 5775 / 6263(约 92%)**,速率掉到 HE-MCS0 NSS1(8.6 Mbit/s) |
| ping 网关 172.20.8.1 | 100% 丢包(ARP 表项仍为 REACHABLE) |
| ping 外网 | 100% 丢包 |
| 路由 | 正确:`223.5.5.5 from 172.20.8.60 via 172.20.8.1 dev phy1-sta0 table 1` |
路由器能收到上游 AP,但发出去的帧绝大多数得不到 ACK,问题在无线链路层,不在路由或防火墙。
对 wpa_supplicant 发一次 `REASSOCIATE`(不换 MAC、不改配置)后,tx failed 立刻归零,速率恢复到 100+ Mbit/s,网关和外网都通。约 5 分钟后再次进入同样的状态,说明这是可复现的问题,不是偶发。
wwan5 在日志中也有连续丢失 224 个 ping、手动重载 Wi-Fi 后才恢复的记录,但更早的日志已被覆盖(日志缓冲只有 128 KB),无法确认原因是否相同。
### 为什么不会自动恢复
1. STA 仍处于关联状态,beacon 正常,wpa_supplicant / netifd 收不到断开事件,不会重连。
2. mwan3 检测到 ping 失败后只把接口标记为 offline、撤掉路由,不会去修复无线链路。
3. 于是链路一直“半死”,直到有人手动重启 Wi-Fi。
### 根因
radio1 的配置是 `htmode='HE40'`,本地热点工作在 40 MHz(主信道 6,中心 2447 MHz),而上游 AP 只给 STA 20 MHz(信道 6,中心 2437 MHz)。同一射频上 AP 与 STA 带宽不一致时,ath11k 的 STA 上行会进入大量 tx failed 的状态。
把 radio1 改成 `HE20` 后,AP 和 STA 都在 20 MHz,上行恢复正常(见“验证”)。
radio0(5G)的情况相同:热点 HE160,STA 40 MHz,观察期间 wwan5 也出现了一次同样的故障。
这是根据对比测试得出的结论,没有在驱动层面确认具体机制。
## 修改内容
所有修改都在路由器上完成,原文件已备份。
### 1. radio1 改为 HE20(根因修复)
```sh
uci set wireless.radio1.htmode='HE20'
uci commit wireless
wifi up radio1
```
代价:2.4G 本地热点的带宽从 40 MHz 降到 20 MHz。
`txpower='13'` 保持不变,改带宽后已经足够。
### 2. 新增无线看门狗 `/usr/sbin/wwan-watchdog`(兜底)
每分钟由 cron 运行一次,mwan3 判定接口 offline 时也会立即触发。逻辑:
| 检测结果 | 处理 |
|---|---|
| 网关 ping 通,接口在线 | 清零计数 |
| 网关 ping 通,mwan3 显示 offline | 只记日志(wwan5 可能是登录过期,重连 Wi-Fi 无用) |
| 网关不通,第 1、2 次 | `REASSOCIATE`(不换 MAC,wwan5 登录状态保留) |
| 网关不通,第 3 次 | `wifi up radioX` |
| 之后仍不通 | 每 10 次检测重启一次射频,避免频繁打断热点 |
相关改动:
- `crontab`:新增 `* * * * * /usr/sbin/wwan-watchdog`
- `/etc/mwan3.user`:追加 `disconnected` 事件时调用看门狗(备份:`/root/mwan3.user.bak`)
- `/etc/sysupgrade.conf`:加入 `/usr/sbin/wwan-watchdog` 和 `/etc/mwan3.user`
日志标签为 `wwan-watchdog`,用 `logread -e wwan-watchdog` 查看。
### 3. 修复 `/etc/hotplug.d/iface/99-fix-same-subnet-routing`
原脚本每次 ifup 只删除当前 IP 的旧规则,旧 IP 的规则会一直残留。wwan2 使用随机 MAC,IP 可能变化,残留越积越多。修改前存在两条残留规则:
```
100: from 172.20.8.214 lookup 2 # 指向 wwan5 的表,错误
100: from 172.20.8.55 lookup 1
```
新逻辑:ifup 时先删除所有满足以下任一条件的 prio 100 规则,再添加当前规则:
- 源 IP 与当前 IP 相同
- 指向同一张路由表
- 源 IP 已不在本机任何接口上
已手动运行一次,残留规则已清除。备份:`/root/99-fix-same-subnet-routing.bak`。
### 4. 日志缓冲调大
`system.@system[0].log_size` 从 128 改为 1024(KB)。原来 mwan3track 的日志约 6 分钟就会把缓冲写满,无法回溯故障。
## 验证
### wwan2:HE20 前后对比
| | HE40(修改前) | HE20(修改后) |
|---|---|---|
| 正常维持时间 | 每次重连后约 5 分钟即失效 | 观察 33 分钟无失效 |
| tx failed | 5775 / 6263(约 92%) | 24 / 31142(< 0.1%) |
| tx 速率 | HE-MCS0 NSS1,8.6 Mbit/s | HE-MCS4–6 NSS2,68–172 Mbit/s |
| 网关 ping | 不通 | 每分钟检测全部通过 |
观察结束时 mwan3:`wwan2 is online (online 00h:33m:09s)`。
### wwan5:看门狗实际触发一次
观察期间 wwan5 出现了同样的故障,看门狗自动恢复:
```
22:00:23 mwan3track 开始连续 ping 失败(共丢 16 个)
22:01:05 wwan-watchdog: wwan5: gateway 10.33.13.1 unreachable ... reassociating
22:01:13 重新关联成功,DHCP 拿回同一个 IP 10.33.13.15(MAC 不变,登录状态保留)
22:01:16 mwan3track: Interface wwan5 is online
```
从断流到恢复约 50 秒,修改前需要手动处理。这也说明 5G 上存在同样的问题(见“未处理 / 后续”)。
### 路由规则
`wifi up radio1` 后 wwan2 换了随机 MAC,IP 从 172.20.8.60 变为 172.20.9.230。新脚本自动删除了旧 IP 的规则:
```
100: from 172.20.9.230 lookup 1
100: from 10.33.13.15 lookup 2
```
## 未处理 / 后续
- **radio0(5G)**:本地热点为 HE160,上游 STA 为 40 MHz,与 2.4G 改动前的情况相同,22:00 也确实出现了一次同样的故障(看门狗已自动恢复)。按 2.4G 的经验,把 radio0 改为 `HE40` 应能根治,但 5G 热点带宽会从 160 MHz 降到 40 MHz,所以暂未修改,目前靠看门狗兜底。可以用 `logread -e wwan-watchdog | grep wwan5` 看触发频率再决定。
- **wwan5 登录过期**:看门狗只记录日志,不会自动登录。如果上游登录页能用简单的 HTTP 表单提交,可以再加自动登录。
- 每天 07:00 的定时重启(`0 7 * * * reboot`)保持不变。
## 回滚
```sh
uci set wireless.radio1.htmode='HE40' && uci commit wireless && wifi up radio1
cp /root/99-fix-same-subnet-routing.bak /etc/hotplug.d/iface/99-fix-same-subnet-routing
cp /root/mwan3.user.bak /etc/mwan3.user
crontab -l | grep -v wwan-watchdog | crontab -
rm /usr/sbin/wwan-watchdog
sed -i '\#^/usr/sbin/wwan-watchdog$#d; \#^/etc/mwan3.user$#d' /etc/sysupgrade.conf
```
## 附录:脚本全文
### `/usr/sbin/wwan-watchdog`
```sh
#!/bin/sh
# Recover Wi-Fi STA uplinks that stay associated but stop passing traffic.
# Called every minute by cron and on mwan3 "disconnected" events.
PATH=/usr/sbin:/usr/bin:/sbin:/bin
STATE_DIR=/tmp/wwan-watchdog
TAG=wwan-watchdog
exec 9>/var/lock/wwan-watchdog.lock
flock -n 9 || exit 0
mkdir -p "$STATE_DIR"
log() { logger -t "$TAG" "$*"; }
gw_ok() {
ping -c 3 -W 1 -I "$1" "$2" >/dev/null 2>&1
}
check() {
local iface="$1" dev="$2" radio="$3"
local count_file="$STATE_DIR/$iface"
local up gw count
up=$(ifstatus "$iface" 2>/dev/null | jsonfilter -e '@.up')
[ "$up" = "true" ] || { rm -f "$count_file"; return; }
gw=$(ifstatus "$iface" | jsonfilter -e '@.route[@.target="0.0.0.0"].nexthop' | head -n 1)
[ -n "$gw" ] || return
if gw_ok "$dev" "$gw"; then
[ -f "$count_file" ] && log "$iface: gateway $gw reachable again"
rm -f "$count_file"
if mwan3 interfaces 2>/dev/null | grep -q "interface $iface is offline"; then
log "$iface: gateway reachable but internet offline (login expired or upstream down?)"
fi
return
fi
count=$(( $(cat "$count_file" 2>/dev/null || echo 0) + 1 ))
echo "$count" > "$count_file"
case "$count" in
1|2)
log "$iface: gateway $gw unreachable via $dev (attempt $count), reassociating"
ubus call "wpa_supplicant.$dev" control '{"command":"REASSOCIATE"}' >/dev/null 2>&1
;;
3)
log "$iface: still unreachable after reassociate, restarting $radio"
wifi up "$radio"
;;
*)
# Back off: retry the radio restart every 10 failed checks
if [ $(( count % 10 )) -eq 3 ]; then
log "$iface: unreachable for $count checks, restarting $radio again"
wifi up "$radio"
fi
;;
esac
}
check wwan2 phy1-sta0 radio1
check wwan5 phy0-sta0 radio0
```
### `/etc/hotplug.d/iface/99-fix-same-subnet-routing`
```sh
#!/bin/sh
[ "$ACTION" = "ifup" ] || exit 0
case "$INTERFACE" in
wwan2|wwan5)
;;
*)
exit 0
;;
esac
# Get the IP address of this interface
IP=$(ifstatus "$INTERFACE" | jsonfilter -e '@["ipv4-address"][0].address' 2>/dev/null)
[ -z "$IP" ] && exit 0
# Get the L3 device name of this interface
DEV=$(ifstatus "$INTERFACE" | jsonfilter -e '@.l3_device' 2>/dev/null)
[ -z "$DEV" ] && exit 0
# Find the routing table number assigned by MWAN3 for this device
TABLE=$(ip rule show | grep "iif $DEV lookup" | grep -o 'lookup [0-9]*' | awk '{print $2}' | head -1)
[ -z "$TABLE" ] && exit 0
# Remove stale prio 100 rules: same IP, same table, or source IP no longer on this router
ip rule show | awk '$1 == "100:" && $2 == "from" { print $3, $5 }' | while read -r OLD_IP OLD_TABLE; do
if [ "$OLD_IP" = "$IP" ] || [ "$OLD_TABLE" = "$TABLE" ] || \
! ip -4 addr show | grep -q "inet $OLD_IP/"; then
ip rule del from "$OLD_IP" lookup "$OLD_TABLE" prio 100 2>/dev/null && \
[ "$OLD_IP" != "$IP" ] && \
logger -t fix-routing "Removed stale rule: from $OLD_IP table $OLD_TABLE"
fi
done
# Add source-based policy route
ip rule add from "$IP" table $TABLE prio 100
logger -t fix-routing "Added rule: from $IP table $TABLE (dev $DEV) for $INTERFACE"
```
### `/etc/mwan3.user`(追加部分)
```sh
[ "$ACTION" = "disconnected" ] && case "$INTERFACE" in
wwan2|wwan5) /usr/sbin/wwan-watchdog & ;;
esac
```