A DNS sinkhole running on an ESP32 microcontroller, built with Rust and esp-idf-svc.
The ESP32 creates its own Wi-Fi access point. Devices connect to it and all DNS queries go through the ESP32, which can block unwanted domains (ads, trackers) and forward legitimate ones upstream.
- ESP32 (any variant β tested on ESP32-U)
- USB cable for flashing
# Install the ESP Rust toolchain
espup install
# Add to your shell profile
echo '. $HOME/export-esp.sh' >> ~/.zshrc
source ~/.zshrc
# Install flashing tools
cargo install espflash ldproxygit clone <your-repo>
cd esp-sinker
cargo run- On your device (phone, laptop), connect to Wi-Fi network
esp-sinker - Password:
12345678 - Your device gets IP
192.168.71.2, the ESP32 is at192.168.71.1
nslookup google.com 192.168.71.1You should see the query appear in the serial monitor.
- Wi-Fi Access Point β ESP32 broadcasts its own network, DHCP assigns IPs to connected clients
- UDP socket on port 53 β listens for incoming DNS queries
- DNS packet parsing β extracts the queried domain name from raw UDP bytes using
dns-parser - Serial logging β prints every DNS query to the monitor for debugging
- Blocklist check β compare queried domain against
blocklist.txt(embedded at compile time) - NXDOMAIN response β craft and send a "domain not found" response for blocked domains
- Upstream forwarding β forward allowed queries to
1.1.1.1and relay the response back - Subdomain matching β block
ads.example.comifexample.comis in the blocklist
esp-sinker/
βββ src/
β βββ main.rs # Wi-Fi init, UDP loop, DNS parsing
βββ blocklist.txt # One domain per line, embedded at compile time
βββ build.rs # embuild linker setup
βββ Cargo.toml
βββ sdkconfig.defaults
βββ .cargo/
βββ config.toml # Target triple, linker config
| Crate | Purpose |
|---|---|
esp-idf-svc |
Wi-Fi, NVS, peripherals |
dns-parser |
Parse raw DNS UDP packets |
anyhow |
Error handling |
log |
Logging |