Add MX8500 report on Wi-Fi uplinks that never recover
This commit is contained in:
283
reports/2026-10-28-linksys-mx8500-wwan-recovery.md
Normal file
283
reports/2026-10-28-linksys-mx8500-wwan-recovery.md
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
# 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
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user