用一块售价仅 2 美元、连外挂内存都没有的微型芯片,去平替一台数百元且功耗更高的软路由或迷你主机,这种极客反差最近在嵌入式圈子里掀起了不小的波澜。

开发者 M-Abozaid 开源的 esp32-c3-adblock 项目,号称在没有 PSRAM 的 ESP32-C3 上实现了媲美 Pi-hole 的网络广告拦截,宣称能在仅约 50KB RAM 的消耗下,完成对多达 53.7万条域名 的拦截匹配,查询耗时仅约 10ms。很多人第一反应是某种神奇的无损内存压缩黑魔法,但拨开光鲜的数字外衣后会发现,这实际上是一场精巧的工程取舍,也是一次代价不小的极限算力代偿。

把战场搬到Flash:5字节哈希的二分法

家庭网络中常见的 DNS 拦截器,如广为人知的 Pi-hole 或 AdGuard Home,通常运行在树莓派或 x86 软路由上。Pi-hole 官方推荐至少 512MB RAM 与 2GB 存储,15 美元起步的树莓派 Zero 2 W 依靠完备的 Linux 系统,将成千上万条完整的域名字符串保存在内存的树状结构中逐字比对,同时还要维持 SQLite 数据库记录查询日志。

常规的单片机片上 SRAM 通常不足 400KB。过去的 MCU 级拦截器若要照搬这种字符串解析思路,必须选配 8 美元级别的 ESP32 或 ESP32-S3 搭配外挂 PSRAM 芯片;同类项目如 engkon6 或 lad1337 的 esp-hole,往往需要借助 128KB Bloom filter 辅助过滤,或者采用 64 位哈希来彻底规避冲突。

esp32-c3-adblock 走了一条极端的路线:它彻底放弃了在内存里处理字符串。

DNS 查询拦截与 Flash 哈希检索流程 DNS 请求接入 提取目标域名 FNV-1a 哈希 截断至 40-bit (5B) Flash 升序二分查找 约 18 次 I/O 读盘 命中黑名单 应答 0.0.0.0 未命中放行 转发上游 DNS 整个过程仅消耗片上 ~50KB SRAM,数据全部驻留并检索于片外 Flash

在规则编译阶段,它将拦截列表中的每一个域名计算出 40 位(5 字节)的 FNV-1a 哈希值,按升序排布直接写死在 Flash 空间中。当网络内有 DNS 请求到达时,MCU 提取域名计算出 40 位哈希,然后在 Flash 的有序列表中进行二分查找。如果命中,直接向客户端返回 0.0.0.0 丢弃;如果没命中,再转发给上游公网 DNS。

因为不需要在内存中装载庞大的字符串树,14.1 万条域名哈希仅占 0.67MB Flash。二分查找在极端情况下只需在 Flash 中进行约 18 次读取,把整机 RAM 需求压缩到了惊人的 50KB。

50万条神话背后的存储账本

然而,宣传口径中那个令人瞩目的 53.7 万条条目,只是纯粹的纸面极限。

在标准的 4MB Flash 容量约束下,53.7 万条 5 字节哈希需要切走整整 2.56MiB Flash。为了塞下这张庞大的哈希表,固件必须采用极端激进的单 App 分区表。这意味着系统失去了用于固件更新的双 App 槽位(A/B 分区),彻底剥夺了固件的无线 OTA 能力。一旦发生意外断电或固件异常,设备无法回退自愈,用户只能把它拔下来重新插上电脑进行物理刷机。

如果希望保留正常的 OTA 救砖与维护能力,双分区划分后留给规则存储的空间仅剩约 1.3MB,实际装载上限会骤降至 25万条域名 左右。

4MB Flash 分区与功能权衡对比 单分区极限配置(53.7 万条) 哈希表体积:约 2.56MiB 系统分区:仅保留单 App 槽位 关键代价:彻底丧失固件无线 OTA 救砖能力 碰撞期望对数约 0.131,约 12% 概率误杀正常域名 双分区稳定配置(约 25 万条) 哈希表体积:约 1.3MB 剩余可用 系统分区:双 App 槽位(A/B 分区自愈) 运维能力:支持安全固件空中升级(OTA) 碰撞概率极低,兼顾家庭日常维护稳定性

更深层的隐患来自数学层面的概率悖论。为什么作者选择 40 位而不是更省空间的 32 位?因为 32 位在 25 万条时就会发生多次碰撞。但在 40 位截断哈希下,根据生日界限计算,53.7 万条记录的理论碰撞期望对数约为 0.131,对应整张表约有 12%的概率 出现至少一次哈希冲突。

哈希碰撞在黑名单场景下是致命的:它会导致未被屏蔽的正常域名与广告域名产生相同的 5 字节指纹,进而被无差别拦截(假阳性误杀)。由于生成脚本只会丢弃重复指纹,在不可逆的哈希结构下,你根本无法在线定位到底哪一个正常网站被误伤了。

  • 风险.盲目追求 53.7 万条极限列表会破坏系统的容错机制;一旦遭遇假阳性误杀,在不可逆的哈希黑盒面前,排查断网原因将无从下手。

阉割后的代偿与网络拓扑盲区

天下没有免费的午餐,将复杂的应用降维至几元钱的微控制器上,必然要剥离现代广告拦截器的关键能力。

正规的 DNS 过滤引擎倚仗复杂的正则表达式(Regex)与修饰过滤规则,支持在封禁整个主域的同时单点豁免特定子域名。而 40 位纯哈希架构是静态且扁平的,它不仅无法执行正则匹配,而且在父域名被屏蔽后,根本无法为某个合法子业务单独挖孔放行。

更严峻的问题潜藏在网络拓扑与硬件稳定性中。许多极客设想将这个两美元的小板子插在路由器背面的 USB 接口上充当备用 DNS(Secondary DNS)。但这是一个广为人知的常识误区:像 Windows 这样的操作系统在处理主副 DNS 时,通常只在主服务器超时后才切换备用,而在更多时候会随机分流轮询。如果 ESP32 响应并返回 0.0.0.0,客户端会认为这是权威有效答复,从而直接打断解析流。

此外,该项目在实测中的基准吞吐量仅约 100 QPS。尽管最新代码加入了 20KB Flash 索引表和小规模哈希结果缓存来优化读取,但在 ESP32-C3 SuperMini 这样发热在 45 至 55 摄氏度、天线紧凑的小板卡上,Wi-Fi 射频在突发 UDP 高并发或供电波动面前缺乏千兆软路由的抗压韧性。加之本地管理后台仅依赖明文 HTTP Basic 认证,并在自动拉取规则时跳过了 TLS 证书校验,将它放在全家流量的咽喉要道,本身就是一种极具风险的裸奔。


极限压榨硬件的巧思固然值得称道,但它更像是一场嵌入式极客的算力杂技,而非免维护的家庭基建设施。
  • 结论.将它作为理解嵌入式存储优化、数据降维与 Flash 检索的玩具是极好的工程范本;但对于真正依赖网络稳定性的家庭环境,一台哪怕最基础的 Linux 盒子依然是更理性的底座。

削足适履者,虽入其屦,必伤其趾。在极端受限的环境下,以空间换时间、以 Flash 代替 RAM 的思路展现了极致的工程智巧;但当这种智巧需要牺牲系统鲁棒性、排错能力以及网络安全作为代偿时,我们更应当看清它华丽数字之下的真实边界。