Het project esp32-c3-adblock is een DNS-sinkhole in de stijl van Pi-hole, specifiek geoptimaliseerd voor de goedkope ESP32-C3 microcontroller.
Technische Kern
In tegenstelling tot andere ESP32-adblockers die blokkeerlijsten in het RAM laden, slaat dit systeem domeinen op als 40-bit FNV-1a hashes in het flashgeheugen. Door gebruik te maken van binair zoeken kunnen tot 537.000 domeinen worden beheerd met een minimale RAM-footprint (~50 KB) en een zoektijd van ongeveer 10 ms.
Belangrijkste Kenmerken
- Hardware: Werkt op elke ESP32-C3 (zoals de SuperMini) van circa $2; PSRAM is niet nodig.
- Beheer: Voorzien van een webdashboard voor statistieken en configuratie, mDNS-ondersteuning (
c3adblock.local) en een captive portal voor WiFi-instellingen.
- Updates: Ondersteunt Over-the-air (OTA) updates voor zowel de firmware als de blokkeerlijsten.
- Beveiliging: Gebruikt HTTP Basic Auth voor wijzigingen en een custom header (
X-Requested-With) ter preventie van CSRF-aanvallen.
Installatie en Gebruik
Het project wordt geflasht via PlatformIO. Gebruikers kunnen eigen blokkeerlijsten genereren via een meegeleverd Python-script dat hosts-bestanden en AdGuard-regels ondersteunt. Voor hardware-bescherming is er een printbaar STL-bestand beschikbaar voor een behuizing.
esp32-c3-adblock: DNS ad-blocker voor de ESP32-C3
De technische aanpak
De kern van dit project is de manier waarop de blokkeerlijst wordt beheerd. In plaats van de lijst in het RAM-geheugen te bewaren, worden de domeinen opgeslagen als gesorteerde 40-bit hashes in het flashgeheugen. Hierdoor passen meer dan 140.000 domeinen in ongeveer 0,7 MB flash, waarbij matches in circa 10 ms worden gevonden en er slechts ~50 KB RAM wordt gebruikt.
Het procesverloop: Query → domein extraheren → FNV-1a hash (+ parent suffixes) → binair zoeken in de flash-hashtabel
- Hit: Antwoord
0.0.0.0 (sinkholed).
- Miss: Doorsturen naar de upstream resolver en het antwoord relayeren.
Waarom deze methode?
De meeste DNS-sinkholes voor de ESP32 laden de blokkeerlijst (domeinstructs) in het RAM, waardoor ze PSRAM vereisen. Dit project gebruikt vaste 5-byte (40-bit) hashes in flash.
| Kenmerk | String-in-RAM benadering | Hash-in-flash benadering (dit project) |
| Hardware | ESP32 + PSRAM (~$8) | ESP32-C3, geen PSRAM (~$2) |
| 141k domeinen | ~2.5 MB RAM | 0.67 MB flash |
| RAM-gebruik | Meestal volledig bezet | ~50 KB |
| Zoektijd | String-vergelijking | ~18 flash-reads (~10 ms incl. WiFi RTT) |
| Collisies | N.v.t. | 0 bij 141k (1 bij 537k) |
De keuze voor 40 bits
40 bits is het ideale punt voor dit flash-budget. Collisies volgen de birthday bound: bij 141.000 domeinen zijn er vrijwel geen collisies, bij 537.000 is er ongeveer één (één onfortuinlijk domein wordt dus onterecht geblokkeerd). Een verlaging naar 32 bits zou 20% flash besparen, maar zou leiden tot circa 7 collisies bij 250.000 domeinen. Een verhoging naar 64 bits zou 3 bytes per domein verspillen om een probleem op te lossen dat er nauwelijks is.
Deze techniek is niet alleen een workaround voor de C3; het werkt op grotere chips. Op een ESP32-S3 met 16 MB flash kunnen deze hashes circa 2,7 miljoen domeinen bevatten, tegenover 466.000 voor strings in 8 MB PSRAM.
Hardware
- Board: Elke ESP32-C3 board (getest op C3 SuperMini), 4 MB flash, geen PSRAM nodig.
- Alternatieven: De klassieke ESP32 (DevKit / WROOM, 4 MB) is ook ondersteund via
pio run -e esp32dev -t upload (community-bijdrage, compile-getest; de C3 is het primaire doelwit).
- Voeding: Gebruik een stabiele USB-bron (telefoonlader of USB-poort van de router). Goedkope of loszittende USB-C → A adapters kunnen zorgen voor brown-outs van de radio tijdens WiFi-transmissie.
Behuizing
Er is een printbaar STL-bestand beschikbaar voor de C3 SuperMini: hardware/esp32-c3-supermini-enclosure.stl.
Printinstructies:
- Geen supports nodig; 0.2 mm layers, ~15% infill.
- Houd het uiteinde van de antenne vrij. De PCB-antenne van de C3 is de zig-zag trace op de korte zijde tegenover de USB-C poort; bedek deze niet met massief plastic of metaal om het signaal (RSSI) niet te verslechteren.
- Laat de ventilatieopeningen open: het board wordt in rust ongeveer 45–55 °C.
Installatie en Flashen (PlatformIO)
Na de eerste USB-flash kunnen zowel de firmware als de blokkeerlijst via WiFi worden bijgewerkt.
Let op: Gebruik een actuele versie van PlatformIO. De gebundelde core in de VSCode extensie of pip install -U platformio in een venv wordt aangeraden. Oude distro/apt packages (bijv. 4.3.4) falen met een AttributeError: ... 'resultcallback'.
Stappenplan:
- Secrets configureren: Kopieer het template en pas het aan:
cp src/secrets.example.h src/secrets.h
WIFISSID / WIFIPASS zijn optioneel (gebruik anders de on-device setup portal).
WEBUSER / WEBPASS / OTA_PASS zijn verplicht. Deze beveiligen de endpoints van het dashboard (/ban, /addblock, /upload, /update, /setupdate, /forgetwifi) en netwerk-OTA.
- Blokkeerlijst bouwen: (Standaard: StevenBlack base + Hagezi Light, ~100k entries)
python3 tools/build_blocklist.py data/blocklist.bin
- Flashen:
pio run -t upload pio run -t uploadfs
- Starten: Open de seriële monitor om het IP-adres te vinden of ga naar
http://c3adblock.local.
Eigen blokkeerlijsten
Het script build_blocklist.py OUT.bin [SOURCE ...] accepteert URL's en lokale bestanden in de volgende formaten:
- Hosts bestanden:
0.0.0.0 ads.example.com tracker.example.com
- Plain domain lists: Eén domein per regel.
- AdGuard / Adblock basisregels:
||ads.example.com^ blokkeert, @@||ok.example.com^ verwijdert een domein.
Een geblokkeerd domein blokkeert automatisch ook alle subdomeinen. Regels die een DNS-hashlijst niet kan uitdrukken (regex, wildcards, $ modifiers, cosmetische ## regels) worden overgeslagen.
Configuratie en Updates
WiFi-instellingen
Indien het apparaat niet kan verbinden (of secrets.h niet is ingesteld), start het een open access point C3-AdBlock-XXXX met een captive portal. Via een telefoon kunnen de netwerkgegevens worden ingevoerd. Om later naar een nieuw netwerk over te stappen: klik op "Forget WiFi" in het dashboard of houd de BOOT-knop ingedrukt tijdens het opstarten.
Over-the-air (OTA) updates
Het dashboard op http://c3adblock.local beheert het volgende:
- Blokkeerlijst: Upload een nieuw
blocklist.bin bestand of stel een URL in voor automatische updates. Er is een standaardlijst beschikbaar die elke maandag wordt bijgewerkt via GitHub Actions.
- Firmware: Upload
firmware.bin via het dashboard of push via de CLI:
pio run -t upload --upload-port c3adblock.local --upload-protocol espota
Trade-off bij 4 MB flash: Firmware OTA vereist twee app-slots, wat ongeveer 1,3 MB overlaat voor de blokkeerlijst (~250k domeinen max). De "ultimate" lijst van 537k domeinen past alleen in een single-app partition table (geen firmware OTA). De keuze hiervoor staat in partitions.csv.
Beveiliging
De read-only weergave (/, /stats.json) is open, maar alle state-changing endpoints vereisen HTTP Basic Auth (WEBUSER/WEBPASS):
/ban, /addblock, /unblock, /forgetwifi
/upload, /update (blokkeerlijst en firmware OTA)
/setupdate, /fetchnow
Netwerk OTA (ArduinoOTA) vereist de OTA_PASS.
Belangrijke beveiligingsdetails:
- HTTP Basic Auth: Dit is een controle voor de LAN-trust-boundary, geen versleuteling. Alles verloopt via plain HTTP op poort 80, omdat de chip geen budget heeft voor een TLS-server. Gevoelige gegevens kunnen door iemand die het LAN-verkeer kan sniffen worden gelezen.
- CSRF-beveiliging: Om te voorkomen dat een externe website via de browser acties uitvoert op het dashboard (CSRF), vereist elk muterend endpoint nu een custom
X-Requested-With: c3-adblock header. Dit is de reden waarom /forgetwifi niet langer een directe URL is die je simpelweg kunt bezoeken.
- Standaard inloggegevens: Indien de placeholders uit
secrets.example.h worden behouden, start het apparaat met publiek bekende wachtwoorden. Dit wordt gemeld in de seriële logs en via een banner op het dashboard.
- WiFi Setup Portal: Het access point voor de initiële setup is onversleuteld, zodat deze zonder wachtwoord toegankelijk is.
Gebruik en Testen
Stel het DNS-adres van een apparaat in op het IP van de C3, of voeg het toe als secundaire resolver.
Testen met dig:
dig @<c3-ip> doubleclick.net → 0.0.0.0 (geblokkeerd)
dig @<c3-ip> github.com → echt IP (doorgestuurd)
Bekende problemen (Gotchas)
- ModemManager (Fedora/Ubuntu): Deze service grijpt vaak
/dev/ttyACM0 aan, wat de C3 reset en seriële communicatie blokkeert.
- Oplossing: Stop ModemManager of voeg een udev-regel toe:
echo 'ATTRS{idVendor}=="303a", ENV{IDMMDEVICE_IGNORE}="1"' | sudo tee /etc/udev/rules.d/99-esp-no-modemmanager.rules
- USB-Serial-JTAG: De console kan vroege boot-output opslokken totdat de host verbinding maakt.
- DNS-compatibiliteit: DNS-clients voegen vaak een EDNS OPT record toe; een geblokkeerd antwoord moet uitsluitend de vraag + antwoord bevatten (
ANCOUNT=1, NSCOUNT=ARCOUNT=0), anders is het malformed.
Status en Toekomst
Voltooid:
- Webdashboard (per-client counts, ban client, custom domains).
- mDNS (
c3adblock.local) voor discovery.
- OTA voor firmware en blokkeerlijsten (incl. scheduled pulls).
- Captive-portal WiFi setup en browser-web-installer.
Toekomstige plannen:
- Bucketed prefix index: Verminderen van flash-reads per lookup van ~18 naar ~1–2 voor betere throughput.
- DHCP-server functionaliteit: Zichzelf aanbieden als DNS voor echte plug-and-play.
Credits & Licentie
Geïnspireerd door s60sc/ESP32_AdBlocker voor het idee om 0.0.0.0 terug te sturen voor geblokkeerde domeinen. Dit is een onafhankelijke implementatie vanaf nul, gericht op hash-in-flash optimalisatie voor chips zonder PSRAM.
Licentie: MIT.
esp32-c3-adblock: DNS ad-blocker voor de ESP32-C3
De technische aanpak
De kern van dit project is de manier waarop de blokkeerlijst wordt beheerd. In plaats van de lijst in het RAM-geheugen te bewaren, worden de domeinen opgeslagen als gesorteerde 40-bit hashes in het flashgeheugen. Hierdoor passen meer dan 140.000 domeinen in ongeveer 0,7 MB flash, waarbij matches in circa 10 ms worden gevonden en er slechts ~50 KB RAM wordt gebruikt.
Het procesverloop: Query → domein extraheren → FNV-1a hash (+ parent suffixes) → binair zoeken in de flash-hashtabel
- Hit: Antwoord
0.0.0.0 (sinkholed).
- Miss: Doorsturen naar de upstream resolver en het antwoord relayeren.
Waarom deze methode?
De meeste DNS-sinkholes voor de ESP32 laden de blokkeerlijst (domeinstructs) in het RAM, waardoor ze PSRAM vereisen. Dit project gebruikt vaste 5-byte (40-bit) hashes in flash.
| Kenmerk | String-in-RAM benadering | Hash-in-flash benadering (dit project) |
| Hardware | ESP32 + PSRAM (~$8) | ESP32-C3, geen PSRAM (~$2) |
| 141k domeinen | ~2.5 MB RAM | 0.67 MB flash |
| RAM-gebruik | Meestal volledig bezet | ~50 KB |
| Zoektijd | String-vergelijking | ~18 flash-reads (~10 ms incl. WiFi RTT) |
| Collisies | N.v.t. | 0 bij 141k (1 bij 537k) |
De keuze voor 40 bits
40 bits is het ideale punt voor dit flash-budget. Collisies volgen de birthday bound: bij 141.000 domeinen zijn er vrijwel geen collisies, bij 537.000 is er ongeveer één (één onfortuinlijk domein wordt dus onterecht geblokkeerd). Een verlaging naar 32 bits zou 20% flash besparen, maar zou leiden tot circa 7 collisies bij 250.000 domeinen. Een verhoging naar 64 bits zou 3 bytes per domein verspillen om een probleem op te lossen dat er nauwelijks is.
Deze techniek is niet alleen een workaround voor de C3; het werkt op grotere chips. Op een ESP32-S3 met 16 MB flash kunnen deze hashes circa 2,7 miljoen domeinen bevatten, tegenover 466.000 voor strings in 8 MB PSRAM.
Hardware
- Board: Elke ESP32-C3 board (getest op C3 SuperMini), 4 MB flash, geen PSRAM nodig.
- Alternatieven: De klassieke ESP32 (DevKit / WROOM, 4 MB) is ook ondersteund via
pio run -e esp32dev -t upload (community-bijdrage, compile-getest; de C3 is het primaire doelwit).
- Voeding: Gebruik een stabiele USB-bron (telefoonlader of USB-poort van de router). Goedkope of loszittende USB-C → A adapters kunnen zorgen voor brown-outs van de radio tijdens WiFi-transmissie.
Behuizing
Er is een printbaar STL-bestand beschikbaar voor de C3 SuperMini: hardware/esp32-c3-supermini-enclosure.stl.
Printinstructies:
- Geen supports nodig; 0.2 mm layers, ~15% infill.
- Houd het uiteinde van de antenne vrij. De PCB-antenne van de C3 is de zig-zag trace op de korte zijde tegenover de USB-C poort; bedek deze niet met massief plastic of metaal om het signaal (RSSI) niet te verslechteren.
- Laat de ventilatieopeningen open: het board wordt in rust ongeveer 45–55 °C.
Installatie en Flashen (PlatformIO)
Na de eerste USB-flash kunnen zowel de firmware als de blokkeerlijst via WiFi worden bijgewerkt.
Let op: Gebruik een actuele versie van PlatformIO. De gebundelde core in de VSCode extensie of pip install -U platformio in een venv wordt aangeraden. Oude distro/apt packages (bijv. 4.3.4) falen met een AttributeError: ... 'resultcallback'.
Stappenplan:
- Secrets configureren: Kopieer het template en pas het aan:
cp src/secrets.example.h src/secrets.h
WIFISSID / WIFIPASS zijn optioneel (gebruik anders de on-device setup portal).
WEBUSER / WEBPASS / OTA_PASS zijn verplicht. Deze beveiligen de endpoints van het dashboard (/ban, /addblock, /upload, /update, /setupdate, /forgetwifi) en netwerk-OTA.
- Blokkeerlijst bouwen: (Standaard: StevenBlack base + Hagezi Light, ~100k entries)
python3 tools/build_blocklist.py data/blocklist.bin
- Flashen:
pio run -t upload pio run -t uploadfs
- Starten: Open de seriële monitor om het IP-adres te vinden of ga naar
http://c3adblock.local.
Eigen blokkeerlijsten
Het script build_blocklist.py OUT.bin [SOURCE ...] accepteert URL's en lokale bestanden in de volgende formaten:
- Hosts bestanden:
0.0.0.0 ads.example.com tracker.example.com
- Plain domain lists: Eén domein per regel.
- AdGuard / Adblock basisregels:
||ads.example.com^ blokkeert, @@||ok.example.com^ verwijdert een domein.
Een geblokkeerd domein blokkeert automatisch ook alle subdomeinen. Regels die een DNS-hashlijst niet kan uitdrukken (regex, wildcards, $ modifiers, cosmetische ## regels) worden overgeslagen.
Configuratie en Updates
WiFi-instellingen
Indien het apparaat niet kan verbinden (of secrets.h niet is ingesteld), start het een open access point C3-AdBlock-XXXX met een captive portal. Via een telefoon kunnen de netwerkgegevens worden ingevoerd. Om later naar een nieuw netwerk over te stappen: klik op "Forget WiFi" in het dashboard of houd de BOOT-knop ingedrukt tijdens het opstarten.
Over-the-air (OTA) updates
Het dashboard op http://c3adblock.local beheert het volgende:
- Blokkeerlijst: Upload een nieuw
blocklist.bin bestand of stel een URL in voor automatische updates. Er is een standaardlijst beschikbaar die elke maandag wordt bijgewerkt via GitHub Actions.
- Firmware: Upload
firmware.bin via het dashboard of push via de CLI:
pio run -t upload --upload-port c3adblock.local --upload-protocol espota
Trade-off bij 4 MB flash: Firmware OTA vereist twee app-slots, wat ongeveer 1,3 MB overlaat voor de blokkeerlijst (~250k domeinen max). De "ultimate" lijst van 537k domeinen past alleen in een single-app partition table (geen firmware OTA). De keuze hiervoor staat in partitions.csv.
Beveiliging
De read-only weergave (/, /stats.json) is open, maar alle state-changing endpoints vereisen HTTP Basic Auth (WEBUSER/WEBPASS):
/ban, /addblock, /unblock, /forgetwifi
/upload, /update (blokkeerlijst en firmware OTA)
/setupdate, /fetchnow
Netwerk OTA (ArduinoOTA) vereist de OTA_PASS.
Belangrijke beveiligingsdetails:
- HTTP Basic Auth: Dit is een controle voor de LAN-trust-boundary, geen versleuteling. Alles verloopt via plain HTTP op poort 80, omdat de chip geen budget heeft voor een TLS-server. Gevoelige gegevens kunnen door iemand die het LAN-verkeer kan sniffen worden gelezen.
- CSRF-beveiliging: Om te voorkomen dat een externe website via de browser acties uitvoert op het dashboard (CSRF), vereist elk muterend endpoint nu een custom
X-Requested-With: c3-adblock header. Dit is de reden waarom /forgetwifi niet langer een directe URL is die je simpelweg kunt bezoeken.
- Standaard inloggegevens: Indien de placeholders uit
secrets.example.h worden behouden, start het apparaat met publiek bekende wachtwoorden. Dit wordt gemeld in de seriële logs en via een banner op het dashboard.
- WiFi Setup Portal: Het access point voor de initiële setup is onversleuteld, zodat deze zonder wachtwoord toegankelijk is.
Gebruik en Testen
Stel het DNS-adres van een apparaat in op het IP van de C3, of voeg het toe als secundaire resolver.
Testen met dig:
dig @<c3-ip> doubleclick.net → 0.0.0.0 (geblokkeerd)
dig @<c3-ip> github.com → echt IP (doorgestuurd)
Bekende problemen (Gotchas)
- ModemManager (Fedora/Ubuntu): Deze service grijpt vaak
/dev/ttyACM0 aan, wat de C3 reset en seriële communicatie blokkeert.
- Oplossing: Stop ModemManager of voeg een udev-regel toe:
echo 'ATTRS{idVendor}=="303a", ENV{IDMMDEVICE_IGNORE}="1"' | sudo tee /etc/udev/rules.d/99-esp-no-modemmanager.rules
- USB-Serial-JTAG: De console kan vroege boot-output opslokken totdat de host verbinding maakt.
- DNS-compatibiliteit: DNS-clients voegen vaak een EDNS OPT record toe; een geblokkeerd antwoord moet uitsluitend de vraag + antwoord bevatten (
ANCOUNT=1, NSCOUNT=ARCOUNT=0), anders is het malformed.
Status en Toekomst
Voltooid:
- Webdashboard (per-client counts, ban client, custom domains).
- mDNS (
c3adblock.local) voor discovery.
- OTA voor firmware en blokkeerlijsten (incl. scheduled pulls).
- Captive-portal WiFi setup en browser-web-installer.
Toekomstige plannen:
- Bucketed prefix index: Verminderen van flash-reads per lookup van ~18 naar ~1–2 voor betere throughput.
- DHCP-server functionaliteit: Zichzelf aanbieden als DNS voor echte plug-and-play.
Credits & Licentie
Geïnspireerd door s60sc/ESP32_AdBlocker voor het idee om 0.0.0.0 terug te sturen voor geblokkeerde domeinen. Dit is een onafhankelijke implementatie vanaf nul, gericht op hash-in-flash optimalisatie voor chips zonder PSRAM.
Licentie: MIT.