esp32-c3-adblock README

The project's README from github.com/M-Abozaid/esp32-c3-adblock. As one markdown file: README.md.

esp32-c3-adblock

A Pi-hole-style DNS ad-blocker that runs on a $2 ESP32-C3 — no PSRAM required.

📰 Featured on Tom's Hardware, XDA Developers, and Korben.

The trick everyone misses: you don't need to keep the blocklist in RAM. Store the domains as sorted 40-bit hashes in flash and binary-search them. 140,000+ domains fit in 0.7 MB of flash and are matched in ~10 ms, using **50 KB of RAM**.

query in ──▶ extract domain ──▶ FNV-1a hash (+ parent suffixes)
         ──▶ binary-search the flash hash table
              ├─ hit  ──▶ answer 0.0.0.0   (sinkholed)
              └─ miss ──▶ forward to upstream resolver, relay the reply

Why this is interesting

Most ESP32 DNS sinkholes load the blocklist (domain strings) into RAM, so they demand PSRAM. This project stores fixed 5-byte (40-bit) hashes in flash instead:

string-in-RAM approach this (hash-in-flash)
Hardware ESP32 + PSRAM (~$8) ESP32-C3, no PSRAM (~$2)
141k domains ~2.5 MB of RAM 0.67 MB of flash
RAM used most of it ~50 KB
Lookup string compare 18 flash reads (10 ms incl. WiFi RTT)
Collisions n/a 0 at 141k (1 at 537k)

Why 40 bits? It's the sweet spot for this flash budget. Collisions follow the birthday bound — at 141k domains you get ~0, at 537k about 1 (i.e. one unlucky domain gets over-blocked). Dropping to 32 bits would save 20% of the flash but cost ~7 collisions at 250k; going to 64 bits wastes 3 bytes per domain to solve a problem you don't have.

The same trick works on bigger chips — it isn't a C3 workaround. On a 16 MB ESP32-S3 these hashes hold ~2.7M domains vs ~466k for strings in 8 MB of PSRAM. Hashes in flash beat strings in PSRAM basically everywhere; the C3 just makes it undeniable.

Hardware

Enclosure

A printable case for the C3 SuperMini: hardware/esp32-c3-supermini-enclosure.stl

Printing notes:

Build & flash (PlatformIO)

One USB flash to get going — after that, firmware and blocklist both update over WiFi (see below).

⚠️ Use a current PlatformIO — the VSCode PlatformIO extension's bundled core, or pip install -U platformio in a venv. The distro/apt platformio package (e.g. 4.3.4) is too old and fails with AttributeError: ... 'resultcallback' (issue #4). A one-click browser installer is on the way (hosting TBD).

# 1. (optional) set WiFi creds at compile time — or skip this and use the
#    on-device setup portal (below). secrets.h is gitignored, stays local.
cp src/secrets.example.h src/secrets.h
#    then edit src/secrets.h -> WIFI_SSID / WIFI_PASS

# 2. build the blocklist hash table (default = StevenBlack base + Hagezi Light,
#    ~140k domains, WhatsApp/social safe)
python3 tools/build_blocklist.py data/blocklist.bin

# 3. flash firmware + the blocklist filesystem (the one and only USB flash)
pio run -t upload
pio run -t uploadfs

# 4. watch it boot, note the IP / open the dashboard
pio device monitor          # -> http://c3adblock.local

WiFi setup (no re-flash needed)

If it can't connect (or you never set secrets.h), it starts an open access point C3-AdBlock-XXXX with a captive portal — join it from a phone, pick your network, type the password, done. To move it to a new network later: open http://c3adblock.local/forgetwifi, or hold the BOOT button while powering on, and the setup portal comes back.

Over-the-air updates (no more USB)

The dashboard at http://c3adblock.local does it all:

4 MB flash tradeoff: firmware OTA needs two app slots, which leaves 1.3 MB for the blocklist (**250k domains max**). The aggressive 537k "ultimate" list only fits the single-app partition table (no firmware OTA). Pick your tradeoff in partitions.csv.

Use it

Point a device's DNS at the C3's IP, or add it as a secondary resolver behind your main DNS. Test:

dig @<c3-ip> doubleclick.net   # -> 0.0.0.0  (blocked)
dig @<c3-ip> github.com        # -> real IP  (forwarded)

Gotchas (learned the hard way)

Done / how it could grow

Credits

Inspired by s60sc/ESP32_AdBlocker — the "answer 0.0.0.0 for blocklisted domains" idea. This is an independent from-scratch implementation focused on the hash-in-flash optimization for PSRAM-less chips.

License

MIT — see LICENSE.

esp32-c3-adblock (M-Abozaid/esp32-c3-adblock)

Searching Database…

这是什么?这是一个 Pi-hole 风格的 DNS 广告拦截固件,可以跑在没有 PSRAM 的 2 美元 ESP32-C3 芯片上,把多达 53.7 万个被屏蔽域名以紧凑哈希的形式存在闪存而不是内存里。
这是装在现有硬件上的软件,还是得买点东西?它是烧录到廉价 ESP32-C3 开发板(例如 C3 SuperMini)上的固件,烧录后这块板子就成了一台带独立网页控制台的独立网络设备。
怎么构建和烧录?用 PlatformIO 通过 USB 构建并烧录一次;之后固件和屏蔽列表都可以通过设备的网页控制台经 WiFi 更新。
烧录之前需要准备什么工具?你需要一个较新的 PlatformIO 安装(VSCode 扩展自带的核心,或用 pip 全新安装),因为发行版/apt 里的旧版 PlatformIO 软件包会失败。
53.7 万域名的大屏蔽列表有什么代价?完整的「ultimate」53.7 万域名列表只能放进 single-app 分区表,而这会禁用固件 OTA 更新;启用 OTA 后,剩余闪存只装得下约 25 万个域名。
什么样的人会选择做这个而不是树莓派 Pi-hole?任何想要一个体积小、便宜、低功耗的 DNS 广告拦截器,直接插在路由器空余的 USB 口上、而不必运行一整套 Pi-hole 服务器的人。
为什么把屏蔽列表以哈希存在闪存里,而不是把域名字符串放进内存?这样可以完全不需要 PSRAM:以 40 位哈希存储时,14.1 万个域名只占约 0.67 MB 闪存和约 50 KB 内存,而原始域名字符串需要约 2.5 MB 内存;查询通过在闪存里做二分查找完成(约 10 毫秒)。
不硬编码凭据的话,WiFi 怎么配置?如果连不上或没有保存的凭据,它会开一个叫 C3-AdBlock-XXXX 的开放热点,并带一个强制门户页面,你可以在上面选择自己的网络并输入密码;开机时按住 BOOT 键会重新打开这个配置门户。
网页控制台能做什么?它显示每个客户端的拦截/放行次数,允许你封禁某个客户端或添加自定义屏蔽域名,并负责固件和 blocklist.bin 文件的 OTA 上传。
有没有可能误封正常域名?有一点风险:40 位哈希有很小的碰撞概率,14.1 万个域名时碰撞约为 0,但完整的 53.7 万域名列表预计约有 1 次碰撞,意味着可能有一个倒霉的域名被误封。
Creator
M-Abozaid
Size
1.5 MB
Stars
3.2K
Age
3mo
Commits · 90 Days