deniz.in

Markets

Weather

Loading weather

· via Hacker News – Front Page (native)

ESP32-C3 DNS ad-blocker sinks 537k domains from flash on a $2 board

An open-source project turns a $2 ESP32-C3 into a Pi-hole-style DNS sinkhole by storing blocklist domains as 40-bit hashes in flash, using about 50 KB of RAM.

ESP32-C3 DNS ad-blocker sinks 537k domains from flash on a $2 board

A developer has open-sourced a DNS ad-blocker that runs on a $2 ESP32-C3 board with no PSRAM at all, delivering Pi-hole-style network-wide filtering from hardware that costs about as much as a coffee. The project, esp32-c3-adblock by M-Abozaid, reached the front page of Hacker News, and its README notes it has been covered by Tom's Hardware, XDA Developers and Korben.

How the blocklist fits in flash

According to the repository, most ESP32-based DNS sinkholes keep the blocklist as domain strings in RAM, which pushes them onto PSRAM-equipped boards. This project takes a different route: each domain is reduced to a 40-bit FNV-1a hash, the hashes are sorted and stored in flash, and incoming queries are resolved with a binary search over that table. A match is answered with 0.0.0.0 — a sinkhole — while anything else is forwarded to an upstream resolver and the reply relayed back to the client.

The figures the README gives: roughly 141,000 domains occupy about 0.67 MB of flash, a lookup takes around 18 flash reads — about 10 ms including the WiFi round trip — and total RAM use sits near 50 KB. A comparable string-in-RAM design would want around 2.5 MB of memory on an $8 PSRAM board.

The 40-bit width is deliberate. Collisions follow the birthday bound, so at 141k domains there are effectively none, while the full 537,000-entry list produces about one — meaning a single innocent domain gets blocked in error. The README reasons that 32-bit hashes would save 20% of the flash but cause around seven collisions at 250k entries, and that 64-bit hashes would spend three extra bytes per domain fixing a problem that does not exist. The technique also scales beyond the C3: on a 16 MB ESP32-S3, hashes in flash hold roughly 2.7 million domains versus about 466,000 for strings in 8 MB of PSRAM.

Building and running it

The tested target is any ESP32-C3 board with 4 MB flash, with a C3 SuperMini used for development; a classic ESP32 DevKit also compiles, though that configuration is community-contributed and compile-tested only. A single USB flash installs the firmware and the blocklist filesystem, after which everything else happens over WiFi: the web dashboard at c3adblock.local handles blocklist uploads, firmware updates and network configuration, with an on-device captive portal for initial WiFi setup. A fresh default blocklist is rebuilt weekly by GitHub Actions and published at a stable URL, so a device can keep its list current on its own.

Blocklists are produced by a Python build script that accepts hosts files, plain domain lists and basic AdGuard/Adblock syntax, including @@ exception rules that remove an exact entry. The default combines the StevenBlack base list with Hagezi Light for roughly 100,000 entries the README describes as safe for WhatsApp and social platforms. Rule types a DNS hash list cannot express — regexes, wildcards, cosmetic filters — are skipped and counted. There is a partition trade-off on 4 MB flash: keeping two app slots for firmware OTA leaves about 1.3 MB for the list, capping it near 250k domains, while the 537k "ultimate" list requires a single-app layout that gives up firmware OTA. Power can come from a router's spare USB port, though the README warns that cheap USB-C adapters can brown out the radio during transmission. A 3D-printable enclosure is included.

Security trade-offs

The README is unusually candid about the device's limits. State-changing dashboard endpoints such as /ban, /upload and the OTA routes now require HTTP Basic Auth, and network OTA needs a separate password; previously these were reachable with no credentials at all, a real concern for a box that sits in the path of every DNS query on the network. A stored-XSS path through custom domain names was closed with HTML escaping. Even so, everything runs as plain HTTP on port 80 — the chip has no realistic budget for a TLS server — credentials are only base64-encoded, and browsers that cache them can be coaxed into sending authenticated requests by unrelated pages.

Why it matters

Beyond ad-blocking, this is a compact lesson in data representation. Choosing fixed-width hashes, sorted flash storage and binary search lets two dollars of silicon do a job normally assigned to a Raspberry Pi, and the collision math used to size the hash is a pattern any embedded developer can borrow. For hobbyists, it is a complete, self-updating, network-wide filter with a bill of materials of exactly one board.

  • #open-source
  • #esp32
  • #dns
  • #ad-blocking
  • #embedded

Related posts