☰
104k 还是 537k:esp32-c3-adblock 广告拦截器的真实代价
2026/10/22 1:28:36 网站建设 项目流程

104k 还是 537k:esp32-c3-adblock 广告拦截器的真实代价

【免费下载链接】esp32-c3-adblockPi-hole-class DNS ad-blocker on a $2 ESP32-C3 (no PSRAM): 537k domains as 40-bit FNV-1a hashes in flash, binary-searched. UDP DNS sinkhole + web dashboard. https://youtube.com/shorts/RaxszOUMi8E?feature=share项目地址: https://gitcode.com/GitHub_Trending/es/esp32-c3-adblock

esp32-c3-adblock 仓库根目录的 blocklist.bin 实测 521,905 字节。每条 40 位哈希占 5 字节,相除之后,这台设备默认携带的黑名单是 104,381 个域名,而不是项目描述里那个 537k。这是一个跑在 2 美元 ESP32-C3 上的 DNS 广告拦截器:把黑名单以排序的 40 位 FNV-1a 哈希存在 flash 里做二分查找,不用 PSRAM,附带能封禁设备、追加域名的 Web 仪表盘。宣传数字都成立,但每一个都有前提,而前提全部写在仓库的文件里。

它做对了什么

这个项目的三个技术决策,是在无 PSRAM 单核芯片的内存预算下把约束用对了:

哈希存 flash 而非字符串存 RAM。传统 ESP32 sinkhole 把域名字符串整个搬进 RAM,十万条以上只能上 PSRAM 板子;这里改用固定 5 字节的 FNV-1a 哈希、排序后存 flash,查询走二分。flash 有 4 MB 且廉价,RAM 才是 C3 上最稀缺的资源,这个取舍把大的那部分放在了便宜的一侧。

两级索引把检索空间压小。src/main.cpp 维护一个 4096 个样本的第一级索引(20 KB,驻留 RAM),单次查询先在 RAM 里定位桶,再从 flash 读最多一块 256 条哈希(1.28 KB)做线性扫描——把"多次随机读 flash"变成"一次块读"。

换表窗口期 fail-open。黑名单替换期间代码故意把 numHashes 置 0(注释原话是 device fail-opens),写入窗口内一律放行。对一个挂在所有 DNS 查询路径上的设备来说,"放行"永远比"误杀"安全。

数字背后的前提:esp32-c3-adblock 的域名上限

默认双 OTA 分区下的 250k 上限

"537k domains as 40-bit FNV-1a hashes" 对应的是 tools/build_blocklist.py docstring 里 "test the limits" 的极限构建。但对照仓库实际值:根目录 blocklist.bin 只有 521,905 字节,默认源(StevenBlack base + Hagezi Light)注释里写的是 ~100k entries。537k 的前提是换单 app 分区表,代价直接写在 partitions.csv:

app0, app, ota_0, 0x10000, 0x150000 app1, app, ota_1, 0x160000, 0x150000 spiffs, data, spiffs, 0x2b0000, 0x150000

默认是双 OTA 布局:两个 app 槽各 0x150000(1.3125 MB),留给黑名单的分区同样是 1.3125 MB,文件注释自己给出的上限是 ~250k domains。537k 需要约 2.69 MB,放不下。.github/workflows/blocklist.yml 的周构建也执行着这条边界:断言产物小于 1,300,000 字节且为 5 的倍数,还要求大于 250,000 字节以"接住悄悄缩水的列表"——默认流水线只产得出 10 万条以内的东西。537k 是能力上限,不是交付规格。

~50 KB RAM 的构成

"no PSRAM, ~50 KB of RAM" 是真的,但拆开看构成:20 KB 的第一级索引就占四成,另有 rangeBuf(1.28 KB)、256 槽直接映射缓存、1536 字节 UDP 缓冲、96 槽客户端表和 200 条自定义域名字符串。这个 50 KB 是索引 + 缓存 + 缓冲的稳态工作集,不是单次查询的开销。它成立的前提是黑名单本体永不进 RAM——这个前提换来一条代价:设备端无法回答"哈希 X 对应哪个域名",仪表盘只有聚合计数,没有逐域明细。

10 ms 查询的真实 flash 读取次数

对比表里 "Lookup: ~18 flash reads (~10 ms incl. WiFi RTT)" 的 18,是 ceil(log2(141k)),即朴素二分的读取次数。源码里实际执行的 inFlash() 已经是两级索引:RAM 索引定位桶,一次 seek + read 收工。README 的路线图还把 "Bucketed prefix index — ~18 → ~1~2 (issue #3)" 列为未完成项——要么是 README 滞后于代码,要么 issue #3 指向更细的粒度;无论哪种,表里的"18"是保守值。

但 "10 ms" 的成分要分开看:命中时本地开销亚毫秒级,耗时大头在 WiFi 链路,以及未命中时向上游的转发。上游地址是 src/main.cpp 里的编译期常量,UPSTREAM_IP 宏默认 Quad9(9.9.9

【免费下载链接】esp32-c3-adblockPi-hole-class DNS ad-blocker on a $2 ESP32-C3 (no PSRAM): 537k domains as 40-bit FNV-1a hashes in flash, binary-searched. UDP DNS sinkhole + web dashboard. https://youtube.com/shorts/RaxszOUMi8E?feature=share项目地址: https://gitcode.com/GitHub_Trending/es/esp32-c3-adblock

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询