花 2 美元买个 ESP32-C3,就能在家里搭一个 Pi-hole 级别的 DNS 广告拦截器,而且不需要外接 PSRAM。最近这个项目在推特上火了一把,被 Tom’s Hardware、XDA 都报道过,GitHub 上已经 3000+ star。

它最绝的地方不是”能拦广告”,而是怎么把 53 万个域名塞进一颗廉价芯片里。

一块 2 美元的小板子,插进路由器 USB 口

这是什么

esp32-c3-adblock 是一个跑在 $2 ESP32-C3 上的 DNS 广告拦截器(DNS sinkhole),作者是 Mahmoud Abozaid,C++ 写的,MIT 协议开源。

它做的事情和 Pi-hole 一样:把广告 / 追踪域名的 DNS 查询拦下来,直接回 0.0.0.0,让设备根本连不上那些服务器。但它不依赖树莓派、不依赖 Docker,一个小 U 盘大小的板子插在路由器 USB 口上就行。

DNS 拦截原理:广告域名回 0.0.0.0,正常域名原样转发真实 IP

核心脑洞:域名不进内存,进 flash

大多数 ESP32 上的 DNS 拦截方案,都是把黑名单(域名字符串)整个加载进 RAM,所以必须配 PSRAM,整套下来要 8 美元左右。

这个项目的思路恰恰相反:把域名算成 40 位的 FNV-1a 哈希,按大小排序存进 flash,查询时用二分查找匹配。

核心脑洞:53.7 万域名以 40 位哈希存进 flash

查询流程大概是这样:

1
2
3
4
查询进来 → 提取域名 → 算 FNV-1a 哈希(带上父域名后缀)
→ 在 flash 哈希表里二分查找
├─ 命中 → 回 0.0.0.0(拦截)
└─ 未命中 → 转发给上游 DNS,原样回传

效果对比:

字符串塞 RAM 方案 本项目(哈希存 flash)
硬件 ESP32 + PSRAM(约 $8) ESP32-C3,无 PSRAM(约 $2)
14.1 万域名 约 2.5 MB RAM 0.67 MB flash
占用 RAM 一大半 约 50 KB
查找方式 字符串比较 约 18 次 flash 读(含 WiFi 往返约 10 ms)
哈希碰撞 无 14.1 万时 0 次,53.7 万时约 1 次

为什么是 40 位?这是 flash 预算下的甜点值。哈希碰撞遵循生日悖论——14 万域名基本不撞,53.7 万大约撞 1 个(也就是有一个倒霉域名会被误杀)。降到 32 位能省 20% flash 但 25 万时就撞 7 个;升到 64 位又纯属浪费。

值得一提的是,这套”哈希存 flash”的思路在更大的芯片上同样成立:16 MB 的 ESP32-S3 能塞下约 270 万域名,而同样 8 MB PSRAM 用字符串只能放约 46 万。

硬件准备

  • 任意 ESP32-C3 开发板(作者实测 C3 SuperMini),4 MB flash,不需要 PSRAM
  • 经典 ESP32(DevKit / WROOM,4 MB)也能编译(社区贡献,C3 才是主测目标)
  • 供电用稳定的 USB 电源(手机充电头或路由器 USB 口都行),劣质的 USB-C→A 转接头会在 WiFi 发射时把射频拉垮
  • 配一个 USB-A → USB-C 转接头,直接插在路由器背面的多余 USB 口上——不用额外电源、不用多一个盒子

还有人做了一个 3D 打印外壳(仓库里 hardware/esp32-c3-supermini-enclosure.stl),打印时注意保持天线那头镂空,别把板载 PCB 天线埋进实心塑料里。

编译与烧录(PlatformIO)

只需要一次 USB 烧录,之后固件和黑名单都能走 WiFi 更新。

⚠️ 用较新的 PlatformIO:VSCode 插件自带的旧 core,或者发行版 apt 装的 platformio(比如 4.3.4)都会报 AttributeError 失败。建议 pip install -U platformio。

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 复制密钥模板(不进 git),按需填写 WEB_USER / WEB_PASS / OTA_PASS(必填)
cp src/secrets.example.h src/secrets.h

# 2. 生成黑名单哈希表(默认 StevenBlack base + Hagezi Light,约 10 万条,已避开 WhatsApp/社交)
python3 tools/build_blocklist.py data/blocklist.bin

# 3. 烧录固件 + 黑名单文件系统(唯一一次 USB 烧录)
pio run -t upload
pio run -t uploadfs

# 4. 看它启动,记下 IP / 打开面板
pio device monitor # -> http://c3adblock.local

黑名单来源很灵活:hosts 文件、纯域名列表、AdGuard / Adblock 基础规则(||ads.example.com^)都支持;@@ 规则用来放行。被拦的域名同时拦掉它的子域名。

Linux 踩坑:Fedora / Ubuntu 默认的 ModemManager 会抢 /dev/ttyACM0 并翻转 DTR/RTS,把 C3 复位、卡住串口。遇到烧录/监视异常先 sudo systemctl stop ModemManager 并加一条 udev 规则忽略该设备。

WiFi 配网不用重烧

如果连不上网(或你压根没填 secrets.h),它会起一个开放热点 C3-AdBlock-XXXX,带 captive portal——手机连上去选网络、输密码就行。以后要换网络,点面板上的 Forget WiFi,或者通电时按住 BOOT 键,配网门户就回来了。

怎么用

把设备的 DNS 指向 C3 的 IP,或者在你的主 DNS 后面把它加为备用解析器。测试一下:

1
2
dig @<c3-ip> doubleclick.net   # -> 0.0.0.0(被拦)
dig @<c3-ip> github.com # -> 真实 IP(已转发)

免 USB 的 OTA 更新

面板 http://c3adblock.local 全包了:

  • 黑名单:直接拖一个 blocklist.bin 上传,或者填一个远程地址让它定时拉。项目用 GitHub Actions 每周一重新生成一份默认名单,固定地址贴一次就一直保持更新:
    https://github.com/M-Abozaid/esp32-c3-adblock/releases/download/blocklist/blocklist.bin
  • 固件:在 Firmware → OTA update 上传 firmware.bin,设备校验后重启进新固件

⚠️ 4 MB flash 的取舍:固件 OTA 需要两个 app 分区,留给黑名单的空间就只剩约 1.3 MB(最多约 25 万域名)。那个激进的 53.7 万”终极名单”只能用在单 app 分区表下(不支持固件 OTA)。在 partitions.csv 里自己权衡。

安全提醒(认真看)

这东西蹲在你家每条 DNS 查询的路径上,安全不能马虎:

  • 面板只读页面(/、/stats.json)对外开放,但所有改状态的接口(/ban、/addblock、/upload、/update、/setupdate、/forgetwifi 等)都要 HTTP Basic Auth(WEB_USER/WEB_PASS)
  • 网络 OTA 还要单独的 OTA_PASS。别用示例文件里的默认密码——那是公开写在仓库里的,设备会在面板和串口告警,但依然会启动
  • 还有一道 CSRF 防线:每个写接口额外要求 X-Requested-With: c3-adblock 头,挡掉网页 <img> / 表单自动带凭证的跨站调用

不过要清醒:这一切都是 HTTP 明文(:80),没有 TLS——这片芯片跑不动 HTTPS 服务器。Basic Auth 的密码只是 base64,不是加密,能嗅探你局域网流量的人(开放 WiFi、ARP 欺骗)就能抓到。所以它防范的是”局域网里其他设备空手调 API”和”浏览器 CSRF”这种常见情况,挡不住已经在链路上的攻击者。别有侥幸心理。

小结

用 2 美元 + 一个 USB 口,给全家网络加一层 DNS 级广告 / 追踪拦截,还能免 USB 远程更新——这个项目的巧思(哈希存 flash)确实值得一看。比树莓派跑 Pi-hole 省电省钱,比纯路由器插件更可控。适合喜欢折腾硬件、又想把家里网络弄干净的人。

注意:出厂开放 AP 和 HTTP 明文是设计取舍,正式用一定改掉默认密码、放在你信得过的网络里。

esp32-c3-adblock 开源 MIT C++

把一个 $2 的 ESP32-C3 变成 Pi-hole 级的 DNS 广告拦截器(DNS sinkhole),无需外置 PSRAM——53.7 万域名以 40 位 FNV-1a 哈希存进 flash 二分查找,还带 Web 管理面板。

★ 3025GitHub Star
⑂ 293Fork
MIT开源协议
无 PSRAM DNS 拦截 Pi-hole 级 53.7 万域名