# v46 / v46.1 换固件开发经历与翻车记录 > 2026-08-17 → 2026-08-19,Cudy TR3000 (MT7981B, ImmortalWrt 24.10, mt76, Linux 6.6.151)。 > 本文记录从 mt_wifi 私有驱动迁到 mt76 开源驱动、最终落地 v46 正式版 + v46.1 UI-only 恢复的完整经历,以及每一处翻车的根因。 --- ## 一、为什么换固件(动机) 老固件(Gzxhwq mt_wifi 系)在 2026-07 的整个 DBDC/HE160 战役后,数据面仍不稳定: - **mt_wifi 私有驱动**:`wifi reload` 卡死、重启后数据面时好时坏(assoc 正常但 0 数据包)、计数器说谎(Tx=0 但流量在跑)、UCI 状态与物理广播不一致。 - **DBDC merge 字段 swap**:UCI 必须反向写才能广播正确,LuCI 显示天然错位,每次开机 ra0/rax0 ↔ dat 绑定还随机。 - **无法 LuCI 完全接管**:改无线要么不生效、要么重启盒子,v25 更是直接变砖(见下)。 用户评估后拍板:**「评估开源的吧」「用官方树重编(干净)」**。 --- ## 二、v25 教训(先把它钉死,别让历史重演) > 用户特别要求把 v25 的教训记进来。它来自 2026-07-20 的 mt_wifi 时代,但教训完全适用 mt76。 **现象**:`sysupgrade-v25.bin` 刷入后**无限重启**,必须 U-Boot 回刷 v11 恢复。 **根因**:`files/etc/hotplug.d/net/99-wifi-band-sync` 这个脚本,在 `ra*/rax*` netdev up 事件里 `uci commit` → `netifd reload wifi` → 新的 netdev up 事件 → hotplug 再触发 → **commit→reload→event 死循环**。 **三条铁律(对一切新固件生效)**: 1. **任何会触发 wifi reload 的逻辑,绝对不允许放进 `hotplug.d/net`**。netdev 事件是 reload 的直接结果,一放进去就是自激回路。 2. **uCI commit 本身不触发 netifd**,结构上不可能循环——只有在 hotplug 事件里主动调 reload 才会。要让状态稳定,要么 commit-only,要么 log-only,绝不 reload。 3. 检测类逻辑要「连续两次读数一致才算稳定」(v24 教训:在驱动 init 中途 30s 抓到瞬态 2.4G 读数把 UCI 改反)。v27 的 commit-only verifier 仍会在 reload 间反复翻 UCI,最终 v28 改 log-only + 显示层 patch 才根治。 --- ## 三、mt76 迁移评估(2026-08-17) 先做研究(`project_mt76_migration_research.md`): - **mt76 就是 TR3000 的官方驱动**(`DEVICE_PACKAGES=kmod-mt7915e`),Cudy 官方即 mt76。 - **下载腰斩 bug**(openwrt/mt76#980):5G 侧已被 commit `0d6eaa4`(2025-09-18,precal-data offset)修复,构建树的 mt76 `PKG_SOURCE_DATE=2025-11-06` 已包含。2.4G 可能略慢于原厂。 - **WED bug**(openwrt/openwrt#20473):WED 开启时可能掉到 ~140Mbps,需要关注。 - **关键决策:不用「嫁接」**——在 mt_wifi 树上恢复 mt76+mac80211-scripts+wifi-scripts 会撞无底洞 patch 冲突(mt_wifi 树用 backports 6.12.6,官方 24.10 用 6.12.96)。**全新克隆官方 `openwrt-24.10` 树 `immortalwrt-mt76`**,mt76 原生无冲突。 --- ## 四、v45.1 六连翻车(2026-08-17 一天六个版本,五个假说全证伪) ### v45.1 — mt76 CLEAN 官方树(md5 `202e4a15...`) 干净 mt76 基线 + 移植 WG/VXLAN/隧道。发现 **mt76 macaddr bug**:驱动读到 multicast 位 MAC(`d1:fd`)→ hostapd 拒 UP。解法:`wifi-iface` 设合法单播 MAC(`fc:a0`),`wifi-device` 层无效。已固化。 ### v45.2 — ip-full 修 VXLAN 空壳(md5 `4dd520ac...`) **症状**:刷 v45.1 后「办公室服务完全访问不了」。**真根因**:v45 用了 `ip-tiny`,`ip link add x type vxlan id 10` 直接报错,netifd vxlan proto 有 bug 建出**空壳 vxlan0**(vni/remote/dstport 全空,帧封装不进 VXLAN)。老 mt_wifi 固件有 `ip-full` 所以 20-vxlan 脚本能跑。**修复**:`CONFIG_PACKAGE_ip-full=y` + 关 ip-tiny + ip-bridge=y。 ### v45.3 — vxlan BUILT-IN 内核(md5 `2b4980ba...`,假说一) **症状**:仍 RX=0。**假说**:`CONFIG_NET_UDP_TUNNEL`/`CONFIG_NET_IP_TUNNEL`/`CONFIG_VXLAN` 是 `=m`(module),built-in 保证 udp_tunnel encap 基础设施先就绪。**修复尝试**:强制 `=y` built-in,去 kmod-vxlan。另烤入两 iface `uapsd=0`(手机 WiFi 51→2ms,这个保留了下来)。 ### v45.6 — 关 JUMP_LABEL(md5 `330c923a...`,假说二) **症状**:仍 RX=0。**kprobe/ftrace 铁证**:VXLAN 回程帧到 wg0 后 `udp_queue_rcv_one_skb` 被调 23×,`udp_encap_enable()` 执行(static_branch_inc++),但 `vxlan_rcv` 从不执行 0× → ARM64 6.6.151 的 jump-label patch 没生效(老 6.6.95 没这 bug)。**修复尝试**:关 `CONFIG_JUMP_LABEL` 让 static_branch_unlikely 退化成普通运行时读 key。**仍失败**。 > 假说三(v45.4/997 VXLAN GRO patch、v45.5-dbg/ftrace+kprobe、v45.6/998 WG GRO bypass)全是内核方向,现场抓取证,最终全部证伪,这些实验 patch 在 v46 全清。 ### 最终真根因(2026-08-18,v46 前夕)—— 不是内核! `vxlan0 RX=0` 的真根因是:**`/etc/config/firewall` 只把 `lan` 放 LAN zone、`wan wan6` 放 WAN zone,`wg0` 不属于任何 zone**。办公室回程的 VXLAN 包从 wg0 进 INPUT,fw4 只接受 br-lan/eth0,`10.99.0.2:4789` 落入 `handle_reject`。所以 WG handshake/transfer 正常、wg0 抓得到 ARP Reply,但 vxlan0 RX 恒 0。 **修复**(授权后现场):`uci add_list firewall.@zone[0].network='wg0'` + 重载 fw4。立即 ping 192.168.1.1 8/8、RX +54、HTTP/HTTPS/DNS 正常、OpenClash fake-IP `198.18.25.135`。 **血泪总结**:v45.1→v45.6 六个版本,两个是环境工具(ip-tiny、macaddr),三个是内核假说(built-in、JUMP_LABEL、GRO/GRO-bypass)**全部证伪**,真凶是**防火墙 zone 归属**——`/etc/config/firewall` 里少写一个 `wg0` 的**最普通的一行配置**。**先查配置,再怀疑内核。** **讽刺的是:为查这个「最简单的地方」,我们把调试下探到了最深——改内核内存**。v45.3 把 `NET_UDP_TUNNEL`/`VXLAN` 强制 built-in;v45.6 关 `CONFIG_JUMP_LABEL` 让 jump-label 不再 patch 内核内存里的代码(ARM64 6.6.151 的 static_branch 代码补丁疑似失效);v45.5-dbg 用 kprobe/ftrace 在内核内存里插桩取证据(`udp_queue_rcv_one_skb` 被调 23×、`vxlan_rcv` 0×)。也就是说,从 `uci add_list` 这一行就能修好的问题,我们一路干到动内核内存——**越简单的根因越容易藏在最深的地方,排查顺序应当反过来:先配置、后路由、再 nft、最后才内核。** --- ## 五、v46 正式版落地(2026-08-18) 全量 `make dirclean` 构建(kernel 6.6.151,`fa661850` 主树 + `350d41da` LuCI feed): - LAN `br-lan`+`eth1`,管理 IP `192.168.1.2/24`;WAN `eth0` DHCP 仅承载自身 + WG underlay - WG `10.99.0.2/32` ↔ 办公室 `10.99.0.1/32`;VXLAN VNI 10/UDP 4789/`nolearning` 入 br-lan - **`wg0` 与 `lan` 同属 LAN zone**(上面真根因的固化) - 零 forwarding、WAN 无 masq、flow offload 关、`kmod-br-netfilter` + MSS clamp 1330 - 小路由 DHCP/RA/DHCPv6/DNS redirect 全关,客户端由办公室 192.168.1.1 供 - 两路 mt76 AP 保留合法单播 MAC + `uapsd=0` - `/etc/config/{network,firewall,dhcp,wireless}` 打包权限 `0600` **产物**:`sysupgrade-v46-formal-mt76.bin`(14,388,023 bytes, SHA-256 `261bcb92...`)。**已刷机、已实机验收**(WG/VXLAN、防泄漏、MSS 1330、mt76 HE160、1GbE 全达标)。 --- ## 六、v46.1 UI-only 恢复(2026-08-19)—— 第二轮翻车 v46 是纯 Bootstrap 基线。用户要恢复 Argon + Bing 壁纸 + WG 隧道插件 + 只读 Link Health 仪表盘。约束:**不恢复 turboacc/HNAT/WARP/BBR/mt_wifi/wrtbwmon 或任何可写加速开关**。 ### 翻车 1:`=m` 只编译不进镜像(本轮最坑) ``` 首次 make → rootfs 与 v46 完全同尺寸 (10000384 bytes),新增文件 0! ``` `.config` 里 5 个 UI 包都是 `=m`(module)。**`=m` 只是「编译成 .ipk 但不装入镜像」,进镜像的包必须是 `=y`**。改成 `=y` + `make defconfig` 重编 → rootfs +408KB,新增 70 文件全 UI。 **教训**:`make` 成功 ≠ 包进镜像。增量构建后用 `diff -rq` 对比新旧 rootfs 清单,0 新增就该立刻警觉。 ### 翻车 2:theme-argon 的 6 张图片被源树「删掉」 feeds/luci 工作区里 `argon.svg` logo、**`bg1.jpg` 壁纸 fallback(159KB)**、`blank.png`、volume×2 全部消失——这就是「登录页背景不显示」的真根因,不是 upstream 没 ship。`git checkout` 恢复。Bing 失败后的本地 `bg1.jpg` fallback 由此生效。 ### 翻车 3:前端 `ReferenceError: push is not defined` Link Health 页面「健康检测全没数据」。根因:后端 ucode 用全局 `push()`,前端 JS 我沿用了 `push(rows, ...)`,但**浏览器 JS 里 `push` 不是全局函数**,必须 `rows.push(...)`。全量扫了三个前端 JS,只此一处。 ### 翻车 4(预览期,已修):两个状态解析 bug - **路由 present=false**:真实路由 `10.99.0.1 dev wg0 scope link` 是正常 host 路由,但 `ip -j` 输出 `dst:"10.99.0.1"`(省略 `/32`),后端只比对 `10.99.0.1/32` 漏判。改为同时接受裸地址与 `/32`。 - **vxlan destination_port=null**:真实 `dstport 4789`,但 iproute2 在 JSON 里**省略默认端口字段**。缺省即等于 4789,缺省按 REQUIRED_VXLAN_PORT 处理。 ### v46.1 产物与验收(全过) - `sysupgrade-v46.1-ui-mt76.bin`(14,797,623 bytes, SHA-256 `629cc6a3...`),K 盘双份 hash 复核一致 - **kernel 成员与 v46 byte-identical**(`366f1c78...`,4,375,268 bytes)——增量构建复用 v46 kernel object,绝不 dirclean - rootfs +408KB:新增 70 文件全 UI,0 删除,**共享文件内容变化 0** - denylist 零变化:`/etc/config/{network,firewall,dhcp,wireless}`(0600)、rc.local、20-vxlan、30-mss-clamp、mss nft 全 hash 一致 - manifest 无禁项:无 turboacc/wrtbwmon/mtkhnat/WARP/mt_wifi/BBR;`iw` 6.9-r1 进镜像(Link Health 依赖) - 烤入的均为硬化版:`rows.push`、`which_bin` 后端、`handleSaveApply:null` 禁用、sysauth local-wins、无 `ui.changes.apply` **状态**:增量构建 + 静态验收通过,**尚未刷机/实机验收**。 --- ## 七、方法论沉淀 1. **先查配置,再怀疑内核**。v45 的三个内核假说全错,真凶是防火墙 zone 里少写一行 `wg0`——**最简单的地方**。为了修这一行,我们下探到改内核内存(built-in、JUMP_LABEL、kprobe 插桩)全白费。排查顺序永远从浅到深:`/etc/config/*` → 路由表 → nft 规则 → 内核。越简单的根因越藏得深。 2. **`=y` 才进镜像,`=m` 只是 .ipk**。构建完必须解包对比 rootfs 清单,0 增删=没烤进去。 3. **前端 JS 别抄 ucode 语法**。`push()`/`match()`/`length()` 在浏览器 JS 里语义完全不同,跨层复用代码前先想清楚运行环境。 4. **netdev/hotplug 里绝不 reload wifi**(v25 铁律)。commit-only / log-only 才安全。 5. **内核验证靠对比,不靠直觉**。kernel 成员 byte-identical 是「没动内核」的唯一可靠证据。 6. **预览期就在 live overlay 上验证前端**,bug 修复后立即烤进固件,避免「预览 OK / 固件又坏」的二次翻车。 --- ## 八、附:v46.1 提交内容(git) 仓库:`K:\SynologyDrive\学习\Openwrt\.v46.1-ui-src\`,commit `ae721f8`: - `luci-theme-argon`(local-wins 登录背景 + bg1.jpg fallback) - `luci-app-argon-config`(去 `ui.changes.apply`、ACL mutator 移 write) - `luci-app-wgtunnel`(rpcd ucode 后端 get/status/prepare/apply/rollback/reconnect + JSONMap 前端 + 60s 一次性 token + 快照回滚,无全局 network ACL) - `luci-app-tr3000-status`(只读 rpcd ucode + 5s 轮询仪表盘 + which_bin + expect-permissive + rows.push) - `audit_ui_packages.py` + `install_preview.sh` + `rollback_watchdog.sh`