20 KiB
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
- 后续:AdGuard Home 查询日志与限流的修改记录在
2026-10-30-cudy-tr3000-adguardhome-memory.md的“Linksys MX8500 检查”一节;总览见README.md
背景
路由器没有有线 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 没有问题。
结论
| 接口 | 状态 | 处理 |
|---|---|---|
| wwan5(5G) | 已解决 | radio0 HE160 → HE40,观察 63 分钟无故障 |
| wwan2(2.4G) | 初步解决,看门狗兜底 | radio1 HE40 → HE20,再去掉 txpower='13' 限制(现 29 dBm)后观察 41 分钟无故障、tx failed 约 0.1%,需更长时间确认。如果仍复发,下次编译固件加入 CONFIG_HE_OVERRIDES,对 wwan2 设置 disable_he=1(见“未处理 / 后续”) |
| 两者 | 自动恢复 | 新增 wwan-watchdog,断流约 2 分钟内自动重连 |
排查结果
故障时的状态(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),无法确认原因是否相同。
为什么不会自动恢复
- STA 仍处于关联状态,beacon 正常,wpa_supplicant / netifd 收不到断开事件,不会重连。
- mwan3 检测到 ping 失败后只把接口标记为 offline、撤掉路由,不会去修复无线链路。
- 于是链路一直“半死”,直到有人手动重启 Wi-Fi。
根因
主要原因:同一射频上,本地热点的频宽比 STA 宽。
| 射频 | 热点(修改前) | STA(上游给的) |
|---|---|---|
| radio1(2.4G) | HE40,中心 2447 MHz | 20 MHz,中心 2437 MHz |
| radio0(5G) | HE160,中心 5815 MHz | 40 MHz,中心 5795 MHz |
AP 与 STA 频宽不一致时,ath11k 的 STA 上行会进入大量 tx failed 的状态。把热点频宽改成和 STA 一致后:
- wwan5(5G,HE40):观察 66 分钟以上未再出现故障。
- wwan2(2.4G,HE20):故障从“每次重连后约 5 分钟”降到“几十分钟一次”,但没有完全消失。
2.4G 剩下最可疑的是 HE(Wi-Fi 6)本身:ath11k 作为客户端连 HE AP 时有已知的兼容问题。但无法在现有固件上验证,原因见“未处理 / 后续”。
以上是根据对比测试得出的结论,没有在驱动层面确认具体机制。
修改内容
所有修改都在路由器上完成,原文件已备份。
1. 热点频宽与 STA 对齐(根因修复)
uci set wireless.radio1.htmode='HE20' # 原 HE40
uci set wireless.radio0.htmode='HE40' # 原 HE160
uci commit wireless
wifi up radio1
wifi up radio0
代价:2.4G 热点从 40 MHz 降到 20 MHz,5G 热点从 160 MHz 降到 40 MHz。高带宽需求由 6 GHz 热点(radio2,HE160)承担,未改动。
radio1 原有 txpower='13',后来去掉了(见“验证”中的 txpower 一节)。
注意:wifi up radioX 会断开该射频上的 STA。如果 SSH 正好走这条上联,命令可能执行到一半就断,应在路由器上放到后台执行(BusyBox 没有 nohup):
( trap '' HUP; uci set ...; uci commit wireless; wifi up radio0 ) </dev/null >/tmp/change.log 2>&1 &
2. 新增无线看门狗 /usr/sbin/wwan-watchdog(兜底)
每分钟由 cron 运行一次,mwan3 判定接口 offline 时也会立即触发。
“不通”指网关和两个外网探测地址(223.5.5.5、1.1.1.1)全部 ping 不通,任一能通就算正常。
| 检测结果 | 处理 |
|---|---|
| 正常 | 清零计数 |
| 网关通,但 mwan3 显示 offline | 只记日志(wwan5 可能是登录过期,重连 Wi-Fi 无用) |
| 不通,第 1 次 | 只记日志,等下一次确认 |
| 不通,第 2、3 次 | REASSOCIATE(不换 MAC,wwan5 登录状态保留) |
| 不通,第 4 次 | wifi up radioX |
| 之后仍不通 | 每 10 次检测重启一次射频,避免频繁打断热点 |
判定故障时,日志会附上当时的 tx packets / tx failed / tx bitrate,便于区分是无线卡死(tx failed 暴涨、速率掉到 MCS0)还是上游问题。
第一版只 ping 网关,且第一次失败就重连。上游 Nomadix 网关偶尔不回 ping,23:11 因此误触发了一次:wwan2 原本正常,被重连后反而断了约 1 分钟。改成上面的逻辑后没有再误触发。第一版备份在 /root/wwan-watchdog.v1.bak。
相关改动:
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。
这些规则也是看门狗能正确判断的前提。看门狗用 ping -I <设备> 检测,本机发出的流量会被 mwan3 按 balanced 策略打 mark,可能被分到另一条上联的路由表,绑定的设备与路由出口不一致时,包就发不出去,造成误判。prio 100 的源地址规则排在 mwan3 的 fwmark 规则之前,把流量固定到正确的表。这一点是在 Cudy TR3000 上启用第二条上联时实测确认的,见 2026-10-28-cudy-tr3000-wwan-watchdog.md。
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 | 不通 | 每分钟检测全部通过 |
之后继续观察到 23:43:
23:22 tx failed 2 / 7500
23:33 tx failed 165 / 20807 # 10 分钟内上涨,前兆
23:36:40 mwan3track 开始连续失败(共丢 22 个)
23:37:09 wwan-watchdog: 网关和探测地址都不通,等待确认
23:38:09 wwan-watchdog: 仍不通,reassociating
23:38:15 wwan-watchdog: link passing traffic again
之后 00:37 又发生一次(这次连接持续了 52 分钟),看门狗第二版的日志记录了故障发展过程:
00:37:09 unreachable (tx=57049 failed=182 rate=1.0 MBit/s), confirming next check
00:37:50 (手动查看)tx failed 628,tx 速率 HE-MCS0 8.6 Mbit/s,网关 ping 不通
00:38:09 still unreachable (check 2, tx=57748 failed=853 rate=8.6 MBit/s HE-MCS), reassociating
00:38:16 link passing traffic again(tx failed 归零,HE-MCS3 68.8 Mbit/s)
一分钟内 tx failed 从 182 涨到 853、速率掉到 MCS0,与最初的故障特征一致。两次都是断流约 1.5 分钟,看门狗自动恢复。同一时间 wwan5 正常(两条 SSID 走同一个 Nomadix 网关),说明是 2.4G 本机无线问题,不是上游故障。HE20 大幅降低了频率,但没有根治 wwan2。
wwan2:去掉 radio1 发射功率限制后
00:41 删除了 radio1 的 txpower='13',实际发射功率从 13 dBm 变为 29 dBm(radio0 原本就是 28 dBm)。
观察期间发生了一次意外重启(见下方“事故”),所以观察从开机算起:00:44 – 01:25,共 41 分钟。
| 项目 | HE20、13 dBm | HE20、29 dBm |
|---|---|---|
| 故障间隔 | 25 – 52 分钟一次 | 41 分钟内 0 次 |
| tx failed / tx packets | 故障前一分钟内 182 → 853 | 56 / 42954(约 0.13%) |
| tx 速率 | 故障时 HE-MCS0 8.6 Mbit/s | HE-MCS8 NSS2 206.5 Mbit/s |
| 看门狗动作 | 每次故障都触发 | 0 次 |
| mwan3 wwan2 状态变化 | offline / online | 只有开机时一次 online |
结果很好,但 41 分钟比之前最长的 52 分钟间隔还短,不能确定已根治,需要观察一天以上。查看方法:
logread -e wwan-watchdog | grep -v crond
logread | grep -E 'mwan3track.*wwan2 .*(offline|online)'
推测:发射功率低时,上行(STA → AP)的帧更容易失败,ath11k 的速率控制退到 MCS0 后没能恢复,进入“半死”状态。
事故:误杀 procd 导致路由器重启(00:44)
停止上一轮监控循环时,在路由器上执行了:
for p in $(pgrep -f 'sleep 60'); do pp=$(awk '{print $4}' /proc/$p/stat); kill $pp $p; done
本意是杀掉监控循环的 shell。但路由器上还有其他已被 init 收养的 sleep 60 进程,它们的父进程是 PID 1(procd),所以实际执行了 kill 1。procd 收到 SIGTERM 会重启系统。
影响:路由器重启一次,两条上联断线约 1 分钟。重启后检查,所有设置都在(HE40 / HE20、去掉的 txpower、crontab、看门狗、路由规则脚本、mwan3.user),wwan2 / wwan5 自动上线,prio 100 规则自动加回。
教训:不要按进程名批量 kill,更不要 kill 查到的父进程。停止远程监控只需结束本机的 ssh 进程。
另外试过把 radio1 改成 HT20:只有热点变成 HT,wwan2 仍然以 HE 连接上游(tx bitrate ... HE-MCS 6),没有意义,已改回 HE20。
wwan5:HE160 时看门狗触发一次,HE40 后稳定
radio0 仍为 HE160 时,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 秒,修改前需要手动处理。
22:40 把 radio0 改为 HE40 后(热点和 STA 都是 40 MHz,中心 5795 MHz):
| 时间 | 连接时长 | tx failed / tx packets |
|---|---|---|
| 22:41 | 52 s | 8 / 14358 |
| 23:02 | 21 min | 49 / 83677 |
| 23:22 | 42 min | 98 / 154419 |
| 23:43 | 63 min | 126 / 228395 |
全程网关 ping 正常,看门狗未触发,mwan3:wwan5 is online (online 01h:02m:50s)。修改前大约 17 分钟就会出故障。
路由规则
每次 wifi up radio1 后 wwan2 都会换随机 MAC 和 IP(172.20.8.60 → 172.20.9.230 → 172.20.11.106),新脚本每次都自动删除了旧 IP 的规则,始终只保留两条:
100: from 10.33.13.15 lookup 2
100: from 172.20.11.106 lookup 1
未处理 / 后续
wwan2:下次编译固件时加入 HE overrides,对 wwan2 关闭 HE
去掉 txpower 限制后 wwan2 暂未复发。如果长时间观察确认不再复发,这一步可以不做;如果仍复发,再按下面的方案处理。
目前 wwan2 靠看门狗兜底:wwan2 只是副线路(mwan3 权重 3),断流期间流量会自动走 wwan5,约 2 分钟内恢复。
要根治,需要让 wwan2 不用 HE(Wi-Fi 6)连上游,只用 HT(Wi-Fi 4)。wpa_supplicant 有网络级参数 disable_he=1,但现有固件不支持:
| 检查项 | 结果 |
|---|---|
| 路由器上的 wpad | wpad-basic-mbedtls |
GET_NETWORK <id> disable_vht |
0(支持) |
GET_NETWORK <id> disable_he |
FAIL(不支持) |
OpenWrt 25.12 wpa_supplicant-basic.config / -full.config |
只有 CONFIG_HT_OVERRIDES=y、CONFIG_VHT_OVERRIDES=y,没有 CONFIG_HE_OVERRIDES |
hostap wpa_supplicant/Makefile |
支持 CONFIG_HE_OVERRIDES → -DCONFIG_HE_OVERRIDES |
所以单纯换成完整版 wpad-mbedtls(本仓库 e749002 已改)也不够,下次编译需要改三处(路径基于 OpenWrt 25.12 源码树):
-
wpa_supplicant 打开 HE overrides。 在
package/network/services/hostapd/files/wpa_supplicant-full.config中,CONFIG_VHT_OVERRIDES=y下面加:# Support HE overrides (disable HE) CONFIG_HE_OVERRIDES=y本仓库的包列表用的是完整版
wpad-mbedtls,改-full.config即可;如果改用 basic 版,要改wpa_supplicant-basic.config。 -
wifi-scripts 把
disable_he传给 wpa_supplicant。 在package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc的setup_sta()里,network_append_vars列表中'disable_vht'旁边加上'disable_he':'disable_ht', 'disable_ht40', 'disable_vht', 'disable_he', 'vht', 'max_oper_chwidth', -
schema 登记
disable_he。 在package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json的properties中加入:"disable_he": { "description": "Disable HE (802.11ax) for this station", "type": "boolean" },不加的话
validate.uc只会打印disable_he is not present in the schema,值仍会传下去,但不会被转换成布尔值。加上更干净。
刷机后在路由器上:
uci set wireless.wifinet_radio1.disable_he='1'
uci commit wireless
wifi up radio1
验证方法:
# 应返回 1,而不是 FAIL
ubus call wpa_supplicant.phy1-sta0 control '{"command":"LIST_NETWORKS"}'
ubus call wpa_supplicant.phy1-sta0 control '{"command":"GET_NETWORK <id> disable_he"}'
# tx bitrate 应显示 MCS x(HT),不再是 HE-MCS
iw dev phy1-sta0 station dump | grep bitrate
# 观察数小时,看门狗不应再触发 wwan2
logread -e wwan-watchdog | grep wwan2
注意:sysupgrade 只保留 /etc/config/ 和 /etc/sysupgrade.conf 里列出的文件,/usr/share/ 下的文件会被新固件替换。所以第 2、3 步必须进固件,在路由器上手改会在下次升级时丢失;而且第 1 步不进固件的话,手改 2、3 也没用。disable_he 只影响 wwan2 这个 STA,不影响 2.4G 热点。wwan5 已经稳定,不需要关 HE。
其他
- wwan5 登录过期:看门狗只记录日志,不会自动登录。如果上游登录页能用简单的 HTTP 表单提交,可以再加自动登录。
- 每天 07:00 的定时重启(
0 7 * * * reboot)保持不变。
回滚
uci set wireless.radio1.htmode='HE40' && uci set wireless.radio0.htmode='HE160' && uci commit wireless
wifi up radio1 && wifi up radio0
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
sed -i '\#/usr/sbin/wwan-watchdog#d' /etc/crontabs/root && /etc/init.d/cron restart
rm /usr/sbin/wwan-watchdog
sed -i '\#^/usr/sbin/wwan-watchdog$#d; \#^/etc/mwan3.user$#d' /etc/sysupgrade.conf
附录:脚本全文
/usr/sbin/wwan-watchdog
#!/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
PROBE_IPS="223.5.5.5 1.1.1.1"
exec 9>/var/lock/wwan-watchdog.lock
flock -n 9 || exit 0
mkdir -p "$STATE_DIR"
log() { logger -t "$TAG" "$*"; }
tx_stats() {
iw dev "$1" station dump 2>/dev/null | awk '
/tx packets/ { p = $3 }
/tx failed/ { f = $3 }
/tx bitrate/ { r = $3 " " $4 " " $5 }
END { print "tx=" p " failed=" f " rate=" r }'
}
# Healthy if the gateway or any probe address answers through this device
link_ok() {
local dev="$1" gw="$2" ip
for ip in "$gw" $PROBE_IPS; do
ping -c 2 -W 2 -I "$dev" "$ip" >/dev/null 2>&1 && return 0
done
return 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 link_ok "$dev" "$gw"; then
[ -f "$count_file" ] && log "$iface: link passing traffic again"
rm -f "$count_file"
if ping -c 2 -W 2 -I "$dev" "$gw" >/dev/null 2>&1 && \
mwan3 interfaces 2>/dev/null | grep -q "interface $iface is offline"; then
log "$iface: gateway reachable but mwan3 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)
log "$iface: gateway $gw and probes unreachable via $dev ($(tx_stats "$dev")), confirming next check"
;;
2|3)
log "$iface: still unreachable (check $count, $(tx_stats "$dev")), reassociating"
ubus call "wpa_supplicant.$dev" control '{"command":"REASSOCIATE"}' >/dev/null 2>&1
;;
4)
log "$iface: still unreachable after reassociate ($(tx_stats "$dev")), restarting $radio"
wifi up "$radio"
;;
*)
# Back off: retry the radio restart every 10 failed checks
if [ $(( count % 10 )) -eq 4 ]; 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
#!/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(追加部分)
[ "$ACTION" = "disconnected" ] && case "$INTERFACE" in
wwan2|wwan5) /usr/sbin/wwan-watchdog & ;;
esac