面向 OpBoot 的第一版固件构建项目。使用官方 ipq40xx/generic →
asus_rt-ac42u 定义,OpenWrt 分支为 openwrt-24.10。
OpenWrt、packages、LuCI、routing 和 UA3F 的提交固定在 sources.env。
| 功能 | 包 / 实现 |
|---|---|
| LuCI HTTPS、中文、Argon Dark 主题 | luci-ssl、luci-app-package-manager、luci-theme-argon、LUCI_LANG_zh_Hans |
| UA3F 图形管理 | UA3F v3.6.0 系列的 ua3f 包自带 Lua LuCI,锁定官方最新提交 7a3869714df6...;保留 luci-compat,首次启动即启用 |
| 锐捷 ePortal | 自定义 ruijie-auth、curl、jsonfilter,LuCI 配置及手动登录/注销 |
| 双 WAN | mwan3、luci-app-mwan3、legacy iptables/ip6tables、ipset;系统防火墙为 firewall4 |
| Android / USB 网卡 / F50 | RNDIS、CDC Ethernet、CDC NCM 驱动 |
| MBIM 备用支持 | kmod-usb-net-cdc-mbim、umbim、luci-proto-mbim,自动带入 WDM |
| USB 打印 | kmod-usb-printer、p910nd、luci-app-p910nd |
| 内存与 TCP | BBR、FQ(24.10 的 kmod-sched)、64 MiB zram 逻辑容量 |
| 时间与 DNS | UTC+8(Asia/Shanghai)、阿里/腾讯/公共 NTP、SmartDNS 与 LuCI 管理页(本地 6053 端口) |
| 维护工具 | watchcat、LuCI 命令、irqbalance、DDNS、TurboACC;watchcat 与 DDNS 默认关闭,升级后保持关闭 |
256 MB RAM 是运行预算;128 MB Flash 并非全部可供镜像使用。
官方设备定义的 IMAGE_SIZE 为 20,439,364 bytes(约 19.5 MiB)。
工作流同时检查镜像存在、大小和完整包清单。
项目不安装 Clash、AdGuard Home、Samba、Docker;不修改设备树或分区布局。 不修改或制作 ART、EEPROM、Factory、calibration、Bootloader 分区内容。
首次启动默认 LAN 为 192.168.5.1,管理用户为 root,密码直接设为
password。无线监管域默认设为澳大利亚(AU)。首次启动会同时启用两个无线
网络,SSID 分别为 ACRH17-2.4G 与 ACRH17-5G。
两个无线网络默认使用 WPA2-PSK,密码为 password。
默认主机名为 DESKTOP-ACRH17,以 Windows 电脑风格出现在 DHCP、局域网
设备列表和部分上游网络记录中。系统时区为 UTC+8(Asia/Shanghai,POSIX
时区字符串为 CST-8)。
无线保持标准 OpenWrt 24.10 配置,不强制 160 MHz,也不修改校准、ART、EEPROM 或其他无线相关受保护分区。
本镜像按 IPv4-only 校园网络使用:LAN 不分配 IPv6 前缀,WAN6 和 LAN 的 DHCPv6/RA/NDP 和 firewall4 的 IPv6 处理已停用,内核也默认关闭 IPv6。这样 不会让自动生成的 WAN6 路由绕过 UA3F 或 mwan3 的 IPv4 策略;需要 IPv6 的用户 应另行构建配置。
默认 LuCI 使用 Argon Dark,页脚包含作者链接:番鼠大王。
推送默认分支 codex/openwrt-24.10 或手动运行 RT-ACRH17 Firmware Matrix:
- 安装 Ubuntu 24.04 构建工具,运行 HTTP 行为测试和 Lua / shell 语法检查。
- 获取固定提交,加入自定义包和 UA3F 构建 / 多 WAN 集成补丁。
make defconfig后强制检查每个请求的功能;未知包名导致失败。make download,从源码编译工具链、内核和软件包。- 并行编译失败时原地
make -j1 V=s,保存两份完整日志。 - 检查两种设备镜像和成品 manifest,再上传固件与日志。
Actions 页面:https://github.com/louis16s/ACRH17-OpenWrt/actions
固件 artifact 内包含镜像、sha256sums、manifest、完整配置、diffconfig、
上游提交锁、编译时写入的 version.txt 和本项目 commit。日志在单独的
build-logs-* artifact 中。
源码构建结果与真实路由器上的启动、无线、USB 和校园认证测试应分别验收。
固件版本号以本次编译的开始时间为准。resolve 作业在 run 开始时取一次
UTC+8 时间戳(YYYYMMDD-HHMM),追加到上游 include/version.mk 的默认版本号
之后,例如 24.10-SNAPSHOT-20260913-2359;四个变体共用同一个时间戳,
便于把一次 Release 里的四份镜像认作同一批。CONFIG_VERSION_CODE 写入对应变体
的短提交号,因此在设备上 cat /etc/openwrt_release 可以同时看到批次和具体来源。
这两个值是 make 变量而非 kconfig 符号,会作为命令行参数传给 make,而不是写进
.config——make defconfig 会把无法识别的符号从 .config 中清掉。
上游改动 version.mk 的默认值写法时,scripts/firmware-version.py 的断言会
直接让构建失败,而不是静默生成一个没有意义的版本号。
| 文件后缀 | 用途 |
|---|---|
asus_rt-ac42u-initramfs-uImage.itb |
仅在 OpBoot 明确支持 FIT/initramfs 的 RAM 加载启动时,用于首次临时启动测试 |
asus_rt-ac42u-squashfs-sysupgrade.bin |
已经正常运行该设备 OpenWrt 之后,用于 LuCI / sysupgrade 标准升级 |
尚未获得这台机器的 OpBoot 版本、镜像接收格式或实际分区布局。 因此不能保证它的网页“固件上传”按钮支持 RAM 测试,也不能把 initramfs 当成可永久安装镜像。若页面没有明确的 RAM 启动功能,应先核实 OpBoot 文档和设备现状,再选择启动方法。
首次测试先使用网线,逐项验证设备型号、LAN/WAN、LuCI、两张无线网卡、 USB 枚举、认证、双 WAN 切换和打印。旧 swconfig 配置不能直接迁移至 24.10 的 DSA;大版本迁移应重新配置网络。
上游升级检查可能因残留的 ASUS jffs2 UBI 卷而拒绝安装。
本项目不会自动处理该布局;遇到拒绝时停止安装并核实固件存储布局。
不得绕过镜像兼容检查,也不得写入上述受保护分区。
插入数据线并在手机或 F50 上启用 USB 网络共享。通过 SSH 只读检查:
lsusb
ip -br link
dmesg | grep -Ei 'rndis|cdc|ncm|mbim|usb'在 LuCI「网络 → 接口」创建 usbwan,选择实际枚举的网卡设备并使用 DHCP
客户端;不要固定假定它一定叫 usb0。将它加入已有 WAN 防火墙区域,
确认该区域开启 masquerading,并设置与主 WAN 不同的接口 metric。
若设备枚举为 MBIM 且存在 /dev/cdc-wdm*,使用 LuCI MBIM 协议并填写实际
APN / SIM 参数。CDC NCM 网卡共享通常直接使用 DHCP;需要 AT 命令拨号的
其他 NCM 调制解调器不等同于手机/F50 的 USB 网络共享模式。
F50 各固件的 USB 模式、供电和线材会影响识别。本版不发送未知的 USB mode-switch 指令。路由器只有一个 USB 端口;同时连接 F50 与打印机通常 需要供电充足的 USB Hub。
先分别验证校园 WAN 与 USB WAN,再在 mwan3 中建立对应的接口、成员和策略。 首版建议主 WAN 优先、USB 备用,测试拔线、恢复和 DNS。mwan3 安装后默认未 启用自启动,完成真实上联配置后再启用服务。
首次启动会把 wan、wanb 映射到 mwan3 中已经存在的逻辑接口,并使用
223.5.5.5、119.29.29.29、114.114.114.114 作为 IPv4 探测目标,要求
至少两个目标成功、连续失败 3 次才判定掉线。校园网若屏蔽公共 DNS,应把
在 LuCI 的对应 mwan3 接口中填写学校网关或可访问的校内 DNS 与公共 DNS。
/etc/config/acrh17 的 mwan3_probe_ip 列表仅作为首次启动模板,修改它再重启 mwan3
不会自动同步;运行期间应直接修改 mwan3 的 track_ip。wan2、usbwan 只作为可配置映射
保留,不会凭空创建上联;在 LuCI 中建立实际接口后,再将它加入 mwan3 的成员
和策略。wan6 / wanb6 默认关闭。
UA3F 在「服务 → UA3F」,保持完整上游 LuCI 界面,首次启动默认启用。
sources.env 锁定官方最新未发布提交 7a3869714df6b46af7b1a52287e6560d4bfbd6ba。
该提交修正 Desync 参数含义并增加可配置 TTL 值;本项目保持重排默认关闭,
因此升级提交不会改变现有默认行为。正式 Release 仍为 v3.6.0。
它在本镜像中可使用 nftables TPROXY;对应 tproxy / queue 内核模块已包含。
构建补丁补齐 luci-base/host 的 po2lmo 依赖和 Build/Prepare 目录创建。
UA 改写只覆盖明文 HTTP。UA3F 不做 TLS 中间人,读不到 HTTPS 请求里的
User-Agent,所以浏览器和绝大多数站点(全 HTTPS)看到的仍是真实 UA;
用 curl http://httpbin.org/user-agent 能验证改写生效,换成 https://
则原样透传。这不是故障,是设计边界——要改写 HTTPS 必须让 UA3F 持有证书。
排查时先确认测试走的是 80 端口,否则很容易误判为「UA3F 失效」。
首次启动默认开启 L3 重写中的 TTL 与 TCP 时间戳两项(l3_rewrite_ttl=1、
l3_rewrite_ttl_value=64、l3_rewrite_tcpts=1)。二者只作用于 SYN——
nft 规则仅把 tcp flags syn 送进 NFQUEUE——所以数据面没有逐包开销。
把 TTL 钉在 64 是为了让转发流量看起来来自一跳之外的 Linux 主机:不钉的话
路由器递减后客户端指纹会露出来(Windows 客户端到达时是 127)。
其余项刻意保持关闭:ipid 在现代 Linux 上是空操作却仍要逐包过队列;
tcpwin 宣告 65535 本身就是个显眼指纹;block_quic 在没有中间人的前提下
没有收益且会打断 HTTP/3;bpf_offload 需要 kmod-sched-bpf,本镜像未包含。
随时可在「服务 → UA3F → L3 重写」逐项关闭验证。
UA3F 原始 TPROXY 标记 0x1c9 与 mwan3 默认 0x3f00 掩码有交集。
本项目把 mwan3 掩码设为 0x3f000000,并对 UA3F 的 nft TPROXY/REDIRECT
标记比较使用低 16 位;TPROXY 本地交付的策略路由使用优先级 100 与
0xffff 掩码,先于 mwan3 的上联路由。补丁保存在
scripts/patch-ua3f-mwan3.py,上游升级时必须复审。
该组合需要实机验证。NFQUEUE 模式及高级连接标记重写未经过双 WAN 联合 验证;开启这些功能前应单独验证。透明代理产生的新上游连接来自路由器, 按 LAN 客户端源地址制定的 mwan3 策略不一定继续适用。
进入「服务 → Ruijie ePortal」,填写校园网页成功登录请求中的字段: server、userId、password、service、queryString、Cookie、Referer 等。 填写的是解码后的字段值,脚本统一进行 form URL 编码;不要再次粘贴 百分号编码的整个 POST 请求体。加密 password 必须来自实际协议流程。
先「保存并应用」,再点击 Login now;手动成功后才启用自动认证。 默认检测端点必须返回 HTTP 204,200 登录页与 302 重定向均判为离线。 自定义检测 URL 也必须遵循 204 约定。
双 WAN 环境应把 Physical WAN device 设置为实际校园出口设备,使检测和 认证绑定校园上联,否则 USB 上网可能掩盖校园掉线。物理设备绑定限制出口, 仍需验证校园路由和 DNS 可达。
配置文件权限为 0600,升级时保留;结果文件放在 tmpfs,权限同为 0600。 不把门户返回内容或认证字段写入 syslog。HTTP 成功但门户返回认证失败时, 脚本会返回失败状态。界面操作区绑定真实的 UCI section。
页面顶部是状态面板,汇总认证服务、校园 WAN 与默认网关、最近一次认证结果、 自动重连和最近一次快捷操作;认证日志折叠在页面底部,一次读取 80 行。 状态胶囊表示链路是否可达,而不是「最近一次请求是否成功」:在线时再点一次 「立即登录」会被门户按重复登录拒绝,该结果照常写入「最近认证结果」,但不会把 已经联网的链路标成失败。只有服务认为链路可用时才重新探测,因此守护进程在 探测失败后的重试路径不会为同一件事多付一次探测超时。
这不是通用校园协议自动发现器:若门户要求动态 RSA、验证码、每次更新的 queryString/Cookie 或会话令牌,需要根据该校园的成功请求进一步适配。
在「服务 → p910nd」选择 /dev/usb/lp0(以实际设备为准),端口 0
对应 TCP 9100。默认保持关闭,连接并配置打印机后启用。
建议把监听地址设置为实际 LAN IP;WAN 区域保持默认拒绝入站,
不要添加公网 9100 放行规则。
客户端使用 RAW / AppSocket 9100 并安装该打印机的厂商驱动。 p910nd 不提供渲染驱动,也不保证所有仅支持专有协议的打印机可用。 若双向模式导致异常,可在 LuCI 切换该选项后重新测试。
「服务 → Ruijie ePortal / 锐捷认证」显示 procd 服务状态、最近认证状态、
校园 WAN 逻辑接口及 IPv4 地址、默认网关、最近认证时间/结果/摘要和自动重连
状态。按钮按用途分成三组,同组按钮并排一行:「认证操作」是立即登录、注销认证、
重新认证;「服务与网络」是重新获取 WAN DHCP、重新抓取 queryString、重启认证
服务、清除最近结果;「密码管理」是验证当前密码、回退到上一个密码、丢弃密码
回退点。分组不只是排版:CBI 会把一个 section 里的选项排成一列,所以同组按钮
必须落在同一个 section 里才会同行。手动操作与后台登录共用一把锁,锁忙时不会
误判为认证失败。所有按钮都在后台以短超时执行,避免 LuCI 因门户超时而卡住;
最近日志仅从 RAM 中的 logread 读取 80 行。
自动恢复支持开机认证、断线重认证、检测及失败重试起始间隔、最大失败次数,以及仅重试、 重启 WAN 逻辑接口或重启认证服务三种失败动作。默认不会重启整台路由器。 认证请求前会先检查 WAN 逻辑接口是否 up 且已获得 IPv4;DHCP 未就绪时仅等待, 不会先执行联网探测再执行门户请求。认证失败采用有限退避:默认从 30 秒开始, 依次为 45 秒、60 秒,之后保持 60 秒;达到配置的最大连续失败次数后,只有重启 WAN 或重启服务动作会清零退避,选择“仅继续重试”会保持封顶间隔。
「系统 → 自定义命令」预置重新认证锐捷、刷新锐捷认证参数、重启 UA3F、重拨
UCI 中配置的 USB WAN、重启 p910nd、查看 USB 设备及查看 USB 网络驱动状态。
USB WAN 默认逻辑接口名为 usbwan,可通过 acrh17.settings.usbwan_interface
调整;命令不会重启校园 WAN。
watchcat 已编译但默认关闭,也没有预置实例。升级保留配置时禁用状态会被重新
应用(/etc/rc.d 不在任何保留清单内,重建 rootfs 会让它复位),不会意外
启用。建议仅为校园主 WAN 建立一个检测实例,并将断线动作设置为重启该网络
接口。检测目标使用学校可访问的稳定
IP 或 URL;不要把 Google、Cloudflare 等作为校园网络唯一检测目标。需要监测
F50 时另建 USB WAN 实例,不要默认同时运行多个实例。
DDNS 页面和脚本已编译但默认关闭,不包含服务商、域名或账号。irqbalance 已 启用并使用包自带的 procd/init 服务,适合 IPQ4019 的四核 CPU,不额外创建 守护脚本。TurboACC 默认启用 BBR,软件和硬件 flow offloading、Shortcut-FE 与 FullCone 均关闭。校园认证 / UA3F / mwan3 模式保持这个设置;只有在纯 NAT 高吞吐场景下,才建议临时在 LuCI 中单独开启软件卸载并逐项验证,硬件卸载不作为 默认方案。
sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc
cat /proc/swaps
/etc/init.d/zram statusBBR 调节路由器自身终止或发起的 TCP(包括代理连接),不会替换纯转发 客户端的拥塞控制算法。64 MiB zram 是压缩交换设备的逻辑容量,实际压缩 页仍消耗 RAM;它不写 Flash。UA3F 的实际内存占用需在并发负载下测量。
LAN 地址为 192.168.5.1。SmartDNS 监听本机 6053,dnsmasq 优先将 DNS
请求转发到该端口,同时保留腾讯 DNSPod 和阿里 DNS 的直连 IPv4 回退;SmartDNS
停止、尚未启动或上游全部失败时,dnsmasq 仍可直接解析。上游服务器和监听端口
可通过 LuCI「服务 → SmartDNS」或 /etc/config/smartdns 调整。该 LuCI 应用
来自固定的 OpenWrt 24.10 LuCI 提交,不依赖额外第三方 feed。prefetch 与
serve_expired 保持可选,建议先观察校园 DNS 对过期缓存的接受情况,再按需启用,
不默认增大缓存。
默认 SmartDNS 上游为腾讯 DNSPod(119.29.29.29、119.28.28.28)和阿里
DNS(223.5.5.5、223.6.6.6);SmartDNS 会在可用上游中选择响应更快的
结果。默认 NTP 为阿里云、腾讯云和 pool.ntp.org。
UA3F 在首次启动时已启用并设为开机启动,默认使用上游 TPROXY 与 FFF
User-Agent 重写规则。若校园认证、银行、流媒体或某个站点出现异常,可先在
LuCI「服务 → UA3F」关闭验证,再按目标域名细化规则。
RT-ACRH17 的 5 GHz 使用 QCA9984,官方 OpenWrt 24.10 设备定义使用
ath10k-ct 与该型号专用 BDF。未发现能在此设备上复现、同时保留 OpenWrt
24.10、UA3F 和 mwan3 的更高性能维护分支。部分 QSDK/NSS fork 面向 IPQ807x
和 IPQ6018,不适用于本机的 IPQ4019;以其他设备的 ath10k 非 CT 固件
替换本机固件可能失去 RT-AC42U 专用 BDF,不能作为默认镜像方案。
- OpenWrt 官方设备定义
- OpenWrt 24.10 USB 模块定义
- mwan3 包和源代码
- p910nd 包和默认配置
- UA3F v3.6.0 最新正式 Release
- UA3F 锁定提交 7a386971
完整构建使用一个矩阵 run,在同一次 Actions 中并行编译 OpenWrt / ImmortalWrt
的 Campus(mwan3)和 Single WAN 四个变体;默认分支的提交会取消旧的矩阵 run,
避免批量同步变体分支时重复占用 runner。每个变体的固件和日志使用
RT-ACRH17-<variant>-<run_id> 命名。其他变体分支仍可在 Actions 中手动触发同一矩阵。
纯 Markdown 修改不触发完整编译。下载目录与 ccache 分开缓存;ccache 通过
CONFIG_DEVEL=y/CONFIG_CCACHE=y 真正接入编译器,并按变体、源码锁和配置隔离,
容量限制为 2 GiB。第三方源码按固定提交浅拉取。
完整构建会生成 fast-base-<commit>,包含专用 standalone ImageBuilder。
只修改 files/ 默认配置或锐捷包的脚本、Lua、CSS 时,可以在同分支手动运行
Fast RT-ACRH17 image,输入成功的完整矩阵 run ID 和 variant。它按所选变体
下载对应的 ImageBuilder,检查源码、包配置与构建
补丁是否匹配,再覆盖当前配置文件和锐捷脚本生成标准 sysupgrade。改变内核、
feeds、软件包清单、主题上游或包 Makefile 时必须重新完整构建。
快速构建只交付 sysupgrade;首次内存启动测试使用对应完整构建的 initramfs。 快速构建不会重新编译内核或重新编译上游主题。固件保留 BBR、irqbalance、UA3F、 SmartDNS;启用 mwan3 或代理重写时应先验证正常路径,再在实机比较软件卸载开关 对吞吐、重写和故障切换的影响,不把加速开关全部打开作为默认优化。
完整矩阵构建成功后,Actions 会自动创建一个以 build-<run_id> 命名的 GitHub
Release。Release 只附加四个变体的 sysupgrade.bin;对应的 initramfs、构建配置和
完整日志仍保留在同一次 Actions 的 artifacts 中。首次测试请先确认 OpBoot 版本是否
支持 RAM/临时启动,并使用同一变体的 initramfs;确认设备可正常启动后再刷对应的
sysupgrade.bin。Release 流程不会生成或写入 ART、EEPROM、Factory、校准或 Bootloader
分区内容。
Argon 通过 UCI mode=dark 强制暗色,64 MiB zram 配置持久化。锐捷页面将门户
返回的认证结果与联网检查分开;HTTP 204 探测成功才记录已联网,注销记录为离线。
codex/openwrt-24.10:OpenWrt Campus,原 main 分支,UA3F + mwan3 校园版。codex/immortalwrt-24.10:ImmortalWrt Campus,UA3F + mwan3 校园版。codex/openwrt-24.10-singlewan:OpenWrt Single WAN,UA3F 单上联版。codex/immortalwrt-24.10-singlewan:ImmortalWrt Single WAN,UA3F 单上联版。
本轮修复 UCI 多值列表读取、锐捷重复开机请求、接口绑定、认证操作互斥、 LuCI 网关字段转义和折叠区域校验提示。包配置与成品 manifest 同时检查明确排除的包, 并检查 libustream 提供者冲突。恢复重试通过行为测试验证。
单上联版保留 UA3F、锐捷、SmartDNS、USB 网络、打印和原有维护工具,省去 mwan3 及其 LuCI 页面。USB 上联仍可在网络接口页面配置;多出口的自动策略切换由校园版提供。 所有版本的软件和硬件转发卸载默认关闭,实际吞吐和联合运行仍需实机验证。
最新已完成产物 build-34676435773 的四种镜像已通过 SHA-256、设备 tar/FIT
结构、SquashFS、包清单、脚本来源及权限核对;详情见 审查记录。
后续矩阵构建先通过 Linux 双 WAN 路由和 NFQUEUE 标记测试,再编译固件。
构建启动时固定四个变体的提交,Release 附带四个 .bin 的 sha256sums。
保留配置升级时,初始化脚本保留已有密码、网络、Wi-Fi 和服务选择;全新安装 继续使用上文的默认配置。旧版本通过已配置的 root 密码识别已安装系统。 首次安装后应修改默认管理密码和无线密码。
TurboACC 在 UA3F 或已启用的 mwan3 存在时关闭不兼容的流量卸载。 SmartDNS 的新安装配置将监听限制到回环设备,缓存上限设为 1024 条且不落盘。 锐捷重认证等待 DHCP 恢复,手动重拨和后台登录共用锁;锁忙不会触发 WAN 故障恢复。
快速打包按所选变体检出源码,只有源码、包配置和补丁指纹与完整构建一致时才复用 ImageBuilder。此次补丁和包版本已变化,必须先完成一次新的完整构建。
锐捷 LuCI 页面重写为状态面板 + 折叠参数区:面板只格式化 CBI 模型一次性取出的
数据,不再为每个字段单独 fork 一条命令;按钮、日志和参数区分区渲染,日志改为
<details> 折叠块。十个按钮全部在真机上按浏览器提交路径实测(只提交被点击的
那个按钮,模拟真实浏览器),均返回 HTTP 200、无 Lua 错误、无表单校验错误,
并在 /tmp/ruijie-auth.action 中留下对应动作和结果。其中「刷新 queryString」
在已联网时、已联网时重复「立即登录」、「注销认证」被门户拒绝、
「回退到上一个密码」在没有回退点时都会如实报告失败,这是门户和本地状态的
真实结果,不是页面故障。
「立即登录」在已联网时会被门户按重复登录拒绝,此前这条失败会把 auth_state
写成 failed,面板因此显示「认证失败」而实际网络通畅。现在只有在服务原本认为
链路可用时才重新探测:探测仍成功就只记录这次尝试,链路状态保持 online。
守护进程的重试路径不受影响,它只在自身探测失败后才调用登录,不会为同一件事
再付一次探测超时。
模板语法检查进入 CI。scripts/verify-template.py 按 luci.template.parser
的规则把 .htm 还原成 Lua 再交给 luac -p:LuCI 只识别 <% %>,写 <%--
会被解析成去空白的 <%- 加一个多余的 -,此前只能在页面上表现为 500。
validate-project.py 现在会先编译模板再跑测试。
TurboACC 与 UA3F 不冲突,且这一结论由构建保证:patch-turboacc-runtime.py 在
UA3F 或 mwan3 启用时把 sw_flow、hw_flow、sfe_flow 全部强制为 0,上游改写
这段逻辑会让构建失败。真机上三项均为 0,bbr_cca=1,规则集中没有 flowtable。
BBR 是 TCP 拥塞控制,与 UA3F 的 TPROXY 正交。
mwan3 已禁用,/etc/rc.d 中没有它的符号链接,也没有进程在运行;
/etc/init.d/mwan3 running 返回 true 只是因为它的 service_running() 检查的是
/var/run/mwan3 目录是否存在,重新安装或异常退出会留下这个过期目录。
mwan3 status 显示 tracking down 是同一原因造成的显示问题,不影响联网。
固件版本号改为编译开始时间,见上文「构建和验证」。
锐捷页面有两处纯粹的渲染故障,功能本身正常,所以只有肉眼看页面才会发现。
面板标题上的深色横条来自 argon:主题全局样式化了裸 header 元素,并挂了一条
header::after 的 2rem 色带,而面板当时正好用 <header> 当容器,色带就落在
标题上。改为 <div> 后消失;页面里仍保留的那个 <header> 是主题自己的页头。
十个按钮原本排成一列,因为 CBI 只能把按钮输出成 .cbi-value,而主题把
.cbi-section-node 布局成纵向 flex。此前负责改成网格的类由 dashboard.js
注入,脚本一旦没跑(缓存、报错、禁用 JS)就退回一列。现在布局写在样式表的
:has() 里,不依赖脚本,并按用途拆成三个 section。两条规则都有测试守着,
因为这类回归在其它测试里完全看不出来:页面能渲染、按钮能点,只是长得不对。
UA3F 复核结论是没有失效,但也不像最初以为的那样工作。它的 UA 改写只作用于
明文 HTTP:同一次 httpbin.org/user-agent 请求,走 http:// 返回 FFF,
走 https:// 原样透传(Mozilla/5.0 (Macintosh…) 与 curl-plain 都实测过)。
UA3F 不做 TLS 中间人,读不到 HTTPS 里的 UA。在如今全站 HTTPS 的环境里,
这个边界会让改写看起来完全没生效——排查时必须先确认测试走的是 80 端口。
真机上还观察到 /var/log/ua3f/ua3f.log 大量 SO_MARK(201): connect: connection timed out,目标是被校园网屏蔽的 Google / Facebook / Twitter 地址,
属于预期,不是故障。
L3 重写按判断只开了 TTL 与 TCP 时间戳,理由与各项取舍见上文「默认启用的 UA3F」。
验证方法与常规做法不同值得一提:两个目标值(TTL 64、去时间戳)在正常网络上
本来就能看到——路由器自身发起的连接 TTL 就是 64——所以直接抓包无法区分
「改写生效」和「本来如此」。改用特征值 77 临时替换 l3_rewrite_ttl_value,
再从 LAN 侧发起连接,在 WAN 出口抓到同一个连接:源地址已被 NAT 成
172.16.67.126,TTL 变成 77,timestamp 选项消失;同一连接在 LAN 侧抓到时
是 TTL 64 且带 timestamp。这条对照同时证明了改写作用于转发流量而不只是
路由器自身,随后已把值改回 64。启用后吞吐无退化(24–27 MB/s),
校园认证保持 online。
上一节修完排版后,页面在真机上仍然是几块灰蒙蒙的卡片。原因不在规则写错,而在
调色板假设了深色背景:#cbi-ruijie .cbi-section 用的是固定的半透明深蓝
rgba(17, 27, 45, .5),而这台路由器上 argon 跑的是默认浅色模式——uci show argon 是空的,90-acrh17 里设置 argon.main.mode=dark 的那段在升级时被守卫
拦下,从未执行。半透明深蓝叠在白底上就是灰色。
修法是让版面自己跟随主题,而不是假设某一种模式。argon 在 dark.css 里用
!important 覆盖了 --bg-gray 与 --bg-light 为 #1e1e1e,所以卡片背景读
var(--bg-light, #fff) 就能自动拿到 #fff 或 #1e1e1e;边框和浅色底纹统一用
中性的 #8898aa 调透明度,在白底和深底上都能看;正文颜色完全不设,继承主题,
避免在深色模式下把深色文字留在深色底上。状态面板是唯一的例外,它在两种模式下
都保持深色——那是页面里唯一刻意突出的元素,因此它的颜色是固定的而不是继承的。
按钮同时换了布局。上一版用 repeat(auto-fit, minmax(9.5rem, 1fr)),1fr 会让
每个轨道一直长到填满整行,于是在宽屏上三个短标签被推到卡片两端,中间空出一大片。
现在改为 flex: 1 1 9.5rem 配 max-width: 14rem:同组按钮等宽、靠左聚拢,
窄屏再自动换行。两条都有测试守着,回归时测试会直接失败。
2.4G 按一次实测扫描选了台,当时的结论是 ch6:ch1 有 4 个同频 AP(最强 -62 dBm,正在用)、ch6 只有 1 个(-84 dBm)、ch11 自己没人但被夹在 ch9(-60)和 ch13(-53)之间、两侧各压掉一半带宽。这个结论在第二天被推翻,见下面 09-15 的 信道一节——写在这里是因为它值得留个记录:那天量出来的数没错,错在把一个会变的 东西当成了常量。5G 保持 ch36 / VHT80 不变:它所在的 80MHz 块(36–48)块内最强干扰只有 -70 dBm,是非 DFS 块里最干净的;149–161 块有 -57,而 52–64 块里有一台 -42 dBm 的强 AP。DFS 块(52–64、100–144)看上去更空,但 AU 规范下需要雷达 检测,一旦触发会强制换台并断线,用弱干扰换这个风险不划算。
锐捷密码当时还停在那个从没通过门户验证的候选值上(status 只输出「位数 + 末
两位」而不输出明文,显示 11 位、末两位 17)。这笔账在下面 09-15 的密码一节结清。
此前所有验证都发生在仓库这一侧:离线镜像的结构与哈希、CI 里的 network namespace
透明改写、Kconfig 符号。这些能证明镜像是对的,不能回答「这台刚刷完的机器现在到底
对不对」——而那正是刷完机站在路由器旁边时唯一想知道的事。scripts/acrh17-doctor.sh
就是补这一段的。
一个脚本,两种跑法,内容完全相同:
# ssh 只自动尝试 id_rsa/id_ecdsa/id_ed25519,这台路由器的钥匙不在其中,所以两种
# 远程脚本都要带 --ssh-key
./scripts/acrh17-doctor.sh --host root@192.168.5.1 --ssh-key ~/.ssh/id_acrh17
ssh root@192.168.5.1 'sh -s' < scripts/acrh17-doctor.sh # 或直接在路由器上跑默认是 check,只读,不改任何配置;fix 在此基础上把安全项修回去。检查按平台与
版本、升级保留、服务开关、网络、UA3F、无线、锐捷认证、软件包与 USB、最近日志九组
展开,覆盖的都是前几节里踩过的坑:sysupgrade 重建 rootfs_data 后 watchcat/ddns/
mwan3 需要重新禁用、90-acrh17 的守卫是否真的跳过了出厂默认、UA3F 或 mwan3 在跑时
TurboACC 的三个卸载必须为 0、UA3F 的 L3 重写三元组、mwan3.globals.mmx_mask 与
0xffff 掩码、dnsmasq 的 DNS 分流与 SmartDNS 的回环监听、锐捷配置 0600 与密码状态
机是否还自洽。
边界是刻意划的。会断线或者会覆盖用户选择的修复不进默认路径:LAN 地址、WAN 协议、
Wi-Fi 密码和 2.4G/5G 信道只报告不修改,无线模式(HT20/VHT80)要 --fix-wifi,
门户重新认证要 --fix-auth。信道不在可修复项里不是漏了:射频环境会变,而写死的信道
在环境变了以后不会自己纠正,只会把人从好信道推到坏信道上(这条是 09-15 踩出来的,
见下面 09-15 的信道一节)。模式是另一类东西——HT20/VHT80 是网卡能力问题,跟邻居
无关,所以照旧强制。
任何模式下都不打印密码明文,掩码规则与 ruijie-password 保持一致(位数 + 末两位)。
fix 的每一项都是幂等的,修完再跑一次 check 应当干净无 FAIL。
tests/test_doctor.py 用假根目录加假 PATH 覆盖它,28 项。其中两条是这套东西能用
的前提:一条逐字节比对 check 前后的整棵假根目录,证明只读模式真的什么都没动;一条
在 fix 之后再跑一次 check,证明修复收敛而不是每次都报同一批问题。远程模式另占
一组:假 PATH 上放一个只记账的 ssh,用它验证 --ssh-key 确实变成了 -i 加
IdentitiesOnly,以及不带钥匙时这两个参数真的不出现。另外一组同步
测试把脚本里的常量钉死在仓库事实上——服务开关列表对 90-acrh17、TurboACC 的键对
patch-turboacc-runtime.py、日志字符串对运行时补丁、DNS 条目对 uci-defaults、
keep.d 条目对 files/lib/upgrade/keep.d/acrh17,上游改了而这里没跟着改就会直接变红。
夹具本身也是被测对象。清理 /var/run/mwan3 那段原本用的是 rmdir,而真机上这个
目录里必然有 iface_state/、iptables_log/、mmx_mask,rmdir 只会报
Directory not empty 然后放弃——修复从上线起就是摆设。它能一路绿到 09-15,是因为
夹具当年用 mkdir 造了个空目录,恰好是 rmdir 唯一删得掉的东西。现在夹具按真机
的样子造残留,rmdir 一改回去那条用例立刻变红(已做反事实验证)。
刷机重建 rootfs_data,/etc/config/ruijie 回到出厂默认:没有门户地址、没有学号、
没有密码,服务是关的。WAN 照样能从校园网领到 IPv4,但那只是个校园内网地址,没通过
ePortal 认证就出不了网——「路由器连不上网」就是这个样子。此时路由器没有外网,opkg
和下载都谈不上,修复只能靠设备上已有的东西完成。
scripts/acrh17-campus-up.sh 和 doctor 一样是一份自包含脚本、两种跑法,只用到
curl、ubus、jsonfilter、uci:
# 从开发机一条命令跑完(ssh 只自动尝试 id_rsa/id_ecdsa/id_ed25519,这台路由器的
# 钥匙不在其中,所以要 --ssh-key)
./scripts/acrh17-campus-up.sh --host root@192.168.5.1 --ssh-key ~/.ssh/id_acrh17 \
fix --user 2021xxxxxxxx --password '<密码>' --password '<另一个候选>'
ssh root@192.168.5.1 'sh -s' < scripts/acrh17-campus-up.sh # 或直接送进去跑它能自己算出来的只有门户地址。未认证时校园网把 HTTP 302 到登录页,那个 Location
里同时带着门户 origin 和本次链路的 query_string:origin 就是 server,路径与查询串
分别由 login_path 和 query_string 承担。两者都绑在收到它的地址与 MAC 上,换台机器
抄过来没用、链路一变就失效,所以只能在路由器上现场抓。ruijie-refresh-query 也从这同一个
302 里取 origin 写进 server(截到第一个 / 为止,端口保留,只认 http/https;capture
只抓不登录时同样写),刷机后不必再靠人手填。reauth 失败则两个字段成对回滚:只回滚 query
会留下「旧串配新门户」,下一次登录就发到一台不认识这个串的门户上。
于是 fix 做它做得了的三件事:写入推导出的门户地址、抓取并校验 query_string、
写入学号(--user),凭据齐了再打开 ruijie.main.enabled、补 rc.d 自启链接、
登录并验证。探针刻意从认证要走的设备出去(--interface),因为门户把 query_string
绑在收到请求的地址上,多 WAN 的机器走默认路由会抓回一条属于另一条链路的串,把还能
用的那条覆盖掉。重定向只有落在 index.jsp 上才被当作门户:透明代理和上级网关也会
302,拿它们的 origin 当 server 会把认证发到一台不认识这台机器的服务器上。
密码默认不经过这个脚本,但 --password 可以给,而且可以重复给。候选密码不是直接写
进 UCI 的,而是逐个交给 ruijie-password set:它拿密码做一次真实登录,被门户拒绝就
把上一个值放回去。所以猜错一个候选不会留下一个坏密码,也不会让「密码对不对」变成
没人验证过的假设——第一个通过验证的留下,第二个才轮到。退出码 2(请求没送达门户,
或答复读不懂)意味着这次尝试没有产生任何关于密码的证据,那就不该拿它当拒绝,脚本
停在那里而不是接着猜下一个;75(另一个认证动作持锁)同理。已经在用的密码一概不动,
重设一个没被投诉过的密码只是白白断一次线。不给 --password 时脚本不碰密码。
这条链有个前因后果不能颠倒:ruijie-password 要求 server 与 username 已经在配置
里,否则请求根本发不出去,last_reason 判成 unknown,密码写入了却没有被验证。脚本
内已经把顺序排好(门户地址 → query_string → 学号 → 密码 → 开服务);手动分三步做时
同样是先 fix、再 ruijie-password set '<密码>'、最后再跑一次 fix,缺凭据时脚本会
把这几步打出来。登录排在起守护进程之前也是同一个道理:守护进程只在真正发请求时拿
认证锁,先起服务会和这次登录抢锁,把一次本来能成的登录变成退出码 75。
tests/test_campus_up.py 52 项,夹具补上了 doctor 那份没有的几样东西:一份会说
Location 的 curl、一个真会按路径取值(而不是永远返回空)的 jsonfilter、一个
能表态在线与否的 ruijie-auth、一个按门户裁决和退出码表态的 ruijie-password。
桩的日志全部落在假根目录之外,所以「check 一个字节都没动」这条断言不会把自己的
日志当成脏改动。九个关键判据做了反事实验证——去掉门户页判据、去掉 --interface、
把登录挪到起服务之后、把 75 当成失败、允许没有密码就开服务、放行缺 mac 的 query、
把候选密码明文打进输出、把「没能验证」当成继续猜、重设一个已在用的密码,对应的用例
都会变红。
上面那套东西是当作「路由器的有线口需要过 ePortal」来写的。09-15 现场核查下来,这个
前提不成立:SCAU 的有线口既不跑 ePortal 也不跑 802.1X,校园侧是按 MAC 放行的(用户
在校园网系统里给路由器开了「无感认证」)。证据是四条互相独立的观察:wan 上 12 秒
抓包 0 个 EAPOL 帧(挂 wpa_supplicant 是没用的);generate_204 直接返回 204,没有
302,也就是根本没有强制门户;拿这套凭据去 InterFace.do?method=login,门户回的是
设备未注册,请在 ePortal 上添加认证设备——它认识这个报文,只是不认识这台机器;广播域
是平的,wan 上看得见 172.16.64.x–172.16.79.x 一整段别人的 ARP 与 mDNS。加上用户
确认的「09-13 23:40 起一直没掉线」,结论是:这台机器的出口靠的是它没变过的 WAN MAC
(04:d9:f5:ea:3d:5c)在白名单里,跟密码对不对无关。
前四条都是「它不需要认证」的旁证。第五条反过来做,主动去断它:在校园网系统里对着这台
设备点「下线」。点三次,逐秒探针抓到三次断线(探针每秒同时探一个裸 IP 和一个域名,
把「通不通」和「DNS 好不好」分开记),每次一秒上下。失败是立刻返回的连接被拒而不是
超时——--max-time 3 的两支探针要是真在等超时,那一条采样会拖到六秒以上,而实测间隔仍
是两秒。被主动拒绝,说明对面确实动了手,不是丢包。
值得注意的是断的这段时间路由器什么都没有做:ruijie-auth 没在跑,WAN 的
carrier_changes 从开机起一直是 1,接口 uptime 跨过整个事件没有重置,DHCP 租约与 IP
(172.16.67.126)都没变,logread 里一个字都没有。所以把它放回去的是校园侧。
这条顺带纠正了一个想当然的说法:「下线」按钮并不是形同虚设,它真的会掐断,只是掐不住。 放行跟着 MAC 走,会话一退掉,下一包流量就把它重新捞起来。能用它触发一次重认证,但 没法用它把路由器踢下线——改动动过出口之后,点一下下线、看它几秒内能不能自己回来, 是个不用跑到路由器旁边的验证手段。
所以 campus-up.sh 和 ruijie-auth 是预案,不是这台机器现在缺的东西。ruijie.main.enabled
保持关闭;doctor 关于门户状态文件的那条 WARN(状态文件写着 authenticating,而链路本身
早就通了)就是这个错位留下的痕迹,不是故障。
密码换成了门户真正认的那个(status 现在只有一行「8 位、末两位 As」)。重点不在换,
在于回退点里原来躺着的是那个从没通过验证的候选值(11 位、末两位 17):只要有人
在 LuCI 点「换回上一个密码」,就会被换到一个错的密码上,而且屏幕上看不出区别——回退
前后打印的都只是一个掩码。处理是让工具自己结账,不用手改 UCI:ruijie-password drop
丢回退点,password_original 置成空。空不是敷衍——这台机器刷完机就是没有密码的,
「最初密码」的真值本来就是空,revert --original 面对空值会明说「这台设备出厂时没有
配置过密码,没有可换回的值」,这正是该说的话。留一个错的锚点反而更糟:它会让下一次
set 不再重新锚定。
2.4G 前一天被定在 ch6(理由是同频干扰比 ch1 低 6 倍)。09-15 复扫三次,结论相反:
ch5 上一台 -45 dBm 的 SCAUNET_1x 把 ch6 压到 -44 dBm,而 ch1 是 -66 dBm,差
20 dB——路由器和它的客户端一直待在当天评估里最差的信道上。教训不是「那次量错了」,
是射频环境会变,而写死的信道不会跟着变。所以 doctor 现在一个字节都不写信道,
--fix-wifi 也不写,只强制 htmode:HT20/VHT80 属于网卡能力,跟邻居无关。
同一次检查还发现 radio1 根本没配 htmode,跑的是 NOHT——2.4G 压根没开 802.11n。
补上 HT20 后客户端协商从传统速率升到 72.2 MBit/s(MCS 7),并顺带停掉了一场持续
很久的 AP-STA-POSSIBLE-PSK-MISMATCH 刷屏(累计 504 条)。这类「配置里少一项」的
问题不会报错,只会安静地慢,所以 doctor 才要按能力值而不是按「有没有配」去比。
真机复跑:64 通过、1 警告(上面那条门户状态错位)、0 失败。