上一篇写完当天,v0.7.0 的安装包就挂上去了,采集器的 .ipk/.apk 也在里面——那句「还没进 release」是我写稿时还没传,现在有了。这篇接着讲它:采集器为什么非得跑在路由器上,以及怎么装。
先说结论式的那句话:不是路由器方便,是内网里别的机器根本看不见那些信号。
别的机器看不见什么
路由器是网络的汇聚点(choke point)。它同时看着 DHCP 租约、NAT 连接、WiFi 关联和 DNS 查询——这四样,你在任何一台普通主机上都拿不到:
dhcp_leases:权威的 hostname ↔ MAC ↔ IP 映射(读 dnsmasq 的/tmp/dhcp.leases)conntrack:「此刻谁在跟谁通信」(读/proc/net/nf_conntrack)hostapd:WiFi 终端的关联信息,带信号强度和连接时长dns_log:被动 DNS 指纹——把探测包全挡掉的设备,照样要做 DNS 解析
最后一条是我觉得最值钱的。一个设备可以拒绝你的 ICMP、关掉 SNMP、过滤 mDNS,只要它还想上网,就得问 DNS。这一条永远在泄漏它的存在。
这四个源在非路由器主机上会干净降级成 no-op(打条日志跳过,不报错),所以你只有两个选择:要么跑在路由器上,要么放弃这四路信号。
我的几个站点各自有路由器,采集器必须落在路由器上,数据回传到中心。问题也就跟着来了——路由器是整个网络里内存最紧张的地方。
54MB 是什么概念
我的采集器节点之一是 NanoPi NEO(ARMv7,512MB 内存)。先说清楚这是块什么板子:能跑 Linux,但可用内存非常有限,而它上面还要跑 dnsmasq、hostapd、防火墙这些。
Go 版采集器在这块板上的实测(2 天 96 轮观察器,2026-10-04 → 10-06,生产流量):
常驻内存区间 38.5 – 62.8 MB,中位数 54.1 MB
本地
agent.db稳态 40 – 47 MB/24 一轮主动扫描 142 – 165 秒
54MB 常驻在一台路由器上是不可接受的——很多路由器的可用内存就一百来兆,一个采集器吃掉一半。而且那 38.5–62.8MB 的区间是 GC 锯齿,意味着你没法做容量规划,只能靠猜。
闪存那边也不好看:40–47MB 的本地库在 NAND + overlayfs 上持续写,磨损是真实的。
换成 Rust 之后的实测
同一块板、同一网段、同样的生产流量,Rust 采集器(mibee-agent-rs/0.1.5,2026-10-09):
二进制:19,988,640 B → 4,795,716 B(4.2 倍)
RSS 中位数:54.1 MB → 11.9 MB(4.5 倍);区间从 38.5–62.8 MB 收到 10.9–12.4 MB
连续运行 2 天 8 小时实时采样:12.6 MiB RSS(systemd 报 11.8 MiB)
/24 扫描:142–165 秒 → 84.8–88.6 秒(约 1.7 倍)
本地
agent.db:40–47 MB → 2.58 MB
比数字更重要的是那个区间——10.9 到 12.4 MB,平的。没有 GC 就没有锯齿,这才是小机型容量规划真正要的东西:你知道它占多少。
扫描快出来的那 1.7 倍,主要不是 Rust 本身快,是SNMP 门控:L2/MIB 那几个探针只对「第一段已经证明它讲 SNMP」的主机跑。Go 引擎对每台不支持 SNMP 的主机都要白白等几轮 walk 超时。加上五个 L2 探针之后 /24 仍是 85 秒(加之前 86 秒),这个数据说明门控是真的有效。
代价:三倍代码
这部分我不想省。
Go 采集器:5,152 行(借力 gosnmp、yaml.v3、Go crypto)
Rust 采集器:15,937 行(自研 BER 编码、SNMPv3 USM + 密钥本地化、AES-256-GCM vault、cron 解析器)
三倍代码是刻意付的代价,换的是两件事:运行期内存没有意外,以及线上格式不再依赖一个分配行为不受我们控制的库。Go 版把线上格式交给库,Rust 版自己拥有它们——也拥有它们的差分测试。
这个差分测试上线当天就回本了。它揪出我们自己 SNMPv3 编码器里的一个潜伏 bug:msgMaxSize 65535 被编成了 FF FF,按 BER 规则那是有符号的 −1。真实 agent 会静默丢弃我们发过的每一个 v3 报文,而自测用的宽容版 echo server 永远不会发现——只有拿真实 net-snmp 对拍才看得见。已修复并加了回归测试。
这就是为什么那次重写的验收门槛设成了「分类器逐字节一致」而不是「功能差不多」。全语料(约 2600 条规则)双种子对拍,加上对真实 net-snmp 的差分(v2c walk 5/5、RFC 4293 回退 5/5、v3 authPriv 5/5,全部逐字节一致)。
装起来
v0.7.0 的采集器有三种形态:tar 包、.ipk、.apk。先确认你的固件用哪个包管理器——OpenWrt 从 24.10 起把 opkg 换成了 apk,iStoreOS 新旧版本两种底子都有:
ssh root@<路由器IP> 'command -v opkg apk; grep DISTRIB_RELEASE /etc/openwrt_release'
然后二选一:
# opkg(OpenWrt 22.03/23.05 底,多数 iStoreOS 现行版)
opkg install /tmp/mibee-agent_0.7.0_arm64.ipk
# apk(OpenWrt 24.10+ 底)
apk add --allow-untrusted /tmp/mibee-agent_0.7.0_arm64.apk
包里的 pre 脚本会做架构闸门(uname 不匹配直接拒绝安装)+ -version 冒烟执行,post 脚本生成 /etc/mibee/agent.yaml(LAN 网段从 uci 推导),但center 的 url 和 token 是留空占位、服务不启动——这是有意的,你得先注册好再让它跑。
注册采集器的令牌在中心上创建(POST /api/v1/agents/tokens,需要管理员令牌),然后把这几个键填进 /etc/mibee/agent.yaml:
center.url:中心的地址center.auth_token:上一步创建的令牌network.name/network.cidr:这台路由器自己的 LAN(比如lan-62/192.168.2.0/24)scanner.discovery.*:那四个路由器专属源,按需开
填完启动:/etc/init.d/mibee-agent enable && /etc/init.d/mibee-agent start,然后 logread -e mibee-agent 看一眼,应该能看到版本串和启动行。
跑起来之后,中心的采集器页长这样:

每个采集器的版本、在线状态、归属网络、最近上报时间都在这里。其中时钟偏移这一列超过 ±60 秒会高亮——对分布式采集来说这个数比看起来重要:时钟漂了,上报时间戳就不可信,心跳判断跟着失真。
踩过的坑
这几个是文档里写着的、也是我真碰上的,按遇到概率排:
ICMP 探测全是
permission denied:OpenWrt 默认net.ipv4.ping_group_range = 1 0,等于把非特权 ping socket 整体禁掉了。一次性修复是写一条 sysctl 配置(0 2147483647),mibee-steward doctor会检查这一项。这个坑的概率接近 100%,装完先查它。bind: address already in use:LuCI 占着 80/443,采集器/中心换到 8080 之类。scp报 /usr/libexec/sftp-server: not found:厂商固件(含 GL.iNet)不带 sftp-server,新版 scp 回退 SFTP 就失败。加-O 强制旧协议。GL.iNet MT2500 上新建的 IPv4 监听握不了手:厂商内核 bug,SYN-ACK 以未填充的
0.0.0.0发出去被对端 RST,v6 正常。该固件上的解法是绑 v6(server.host: "::"),或者干脆让路由器只跑采集器、中心放别处。这台机器我最后就是这么用的。database is locked (SQLITE_BUSY):WAL 模式下高并发探测的写冲突。连接池上限是编译期固定值,实际可调的是降低scanner.max_concurrent_hosts- MIPS 路由器直接劝退:
modernc.org/libc(纯 Go SQLite 后端的传递依赖)没有可用的 mips 移植,老款 ath79/ramips(Archer C7、R7000 那批)不在支持之列。ARM/ARM64 才行。
顺带一个好消息:v0.7.0 第一次提供了armv7 的采集器二进制(mibee-agent-linux-armv7)。我那块 NanoPi NEO 就是 armv7,以前得自己交叉编译,现在直接下载就行。
现在还做不到什么
MIPS 架构不支持,是
modernc/libc的结构性限制。要支持得把 SQLite 后端换成 bbolt 或 goleveldb,那是真正的重构,我推迟到真有 MIPS 需求时再说。.ipk/.apk是手工组装的二进制包,不是 OpenWrt buildroot 官方产物。包内标Architecture: all,真正的架构闸门在 pre 脚本的 uname 检查 + 冒烟执行;精确的逐目标架构命名、签名和软件源(feed)订阅还没做。能用,但别指望opkg update装到它。采集器的本地 DB 路径没有独立配置项,固定是配置文件同目录下的
agent.db。想挪到 tmpfs 得把整个配置目录搬过去,重启后配置要重建——所以我的建议是留在闪存,它写量很小(只暂存扫描结果),断连期间还有内存队列(100 批)顶着。hostapd 的 WiFi 终端源还没做活体验证。测试里解析的是真实的
hostapd_cli/iw抓包,但我手上还没有带 AP 的实测环境,这条只能算「代码写完了、没在活机上验过」。行覆盖率 74.3%(2026-10-09,
cargo llvm-cov),Rust 侧的覆盖率棘轮还没做成 CI 门禁。这个数字其实低估了验证强度——分类器和 SNMP 层是全语料字节对拍把门,行覆盖率看不见它们。它是单人项目。GitHub 上 485 次提交全部出自我一个人,响应速度取决于我的时间和状态。有问题可以提 issue,不保证当天回。
实测记录
两个节点的切换时间是 2026-10-06:NanoPi NEO(armv7,systemd)和 FastRhino R68S(arm64,走 iStoreOS 的网页包通道)。从那之后的 0.1.0 → 0.1.5 五次生产升级:零失败升级、零服务重启(整个窗口 NRestarts=0)、零扫描节奏缺失。
随机 MAC 守卫、多宿主稳定性修复、TLS-CN 主机名通道这几个改动都是在这个窗口里发出来并实测过的——也就是说,上一篇里写的那些修复,是先在路由器上跑过才进的版本。
项目地址
仓库(AGPL-3.0):https://github.com/Mi-Bee-Studio/MiBeeSteward
Releases(v0.7.0,采集器有 amd64 / arm64 / armv7 三个二进制和路由器包):https://github.com/Mi-Bee-Studio/MiBeeSteward/releases
Rust 采集器完整对比报告(这篇里每个数字的出处):https://github.com/Mi-Bee-Studio/MiBeeSteward/blob/main/docs/zh/agent-rs.md
OpenWrt 部署文档(安装、排障表、资源占用):https://github.com/Mi-Bee-Studio/MiBeeSteward/blob/main/docs/zh/openwrt.md
如果你手上也有一台吃灰的 ARM 路由器,它是能当网络探针用的——4.8MB 的二进制,12MB 常驻内存。跑起来之后告诉我哪些设备没被认出来,这类反馈对指纹库的贡献最直接,我这边也能收到 star。
我在「蓝宝石的傻话」写一个人维护几张异地局域网的实战:自托管、网络、开源项目的开发和踩坑。这个系列上一篇是《86 台设备,我手写的清单里至少 6 条是错的》,讲这套东西解决什么问题;再往下想写的是指纹规则怎么写——你自己认设备那条路也能走通。