// C3 AdBlock — DNS sinkhole + web dashboard for the ESP32-C3 (no PSRAM). // Blocklist = sorted 40-bit FNV-1a hashes in flash, binary-searched. // Dashboard at http://c3adblock.local : per-client stats, system info, // ban clients, add custom block domains. All control state persisted to flash. #include #include #include #include #include #include #include // firmware OTA #include // remote blocklist fetch #include // https fetch #include // network firmware flashing (pio run over wifi) #include // captive-portal catch-all DNS #include // NVS store for provisioned WiFi creds #include "lwip/etharp.h" #include "lwip/netif.h" #include "secrets.h" // WIFI_SSID / WIFI_PASS — used only as a FALLBACK if no creds // have been provisioned via the captive portal (copy secrets.example.h) // ---- config ---- #ifndef UPSTREAM_IP #define UPSTREAM_IP 9, 9, 9, 9 // Quad9 #endif #ifndef UPSTREAM_PORT #define UPSTREAM_PORT 53 #endif static const IPAddress UPSTREAM(UPSTREAM_IP); static const uint16_t DNS_PORT = 53; static const char* BLOCKLIST_PATH = "/blocklist.bin"; static const int HASH_BYTES = 5; static const uint64_t HASH_MASK = (1ULL << (HASH_BYTES * 8)) - 1; static const int INDEX_ENTRIES = 4096; // 20 KB first-level flash index static const int CACHE_SIZE = 256; // must be power of 2 static const int MAX_RANGE = 256; // max hashes per index bucket (fine up to ~1M hashes; flash holds far fewer) // ---- globals ---- WiFiUDP dnsServer, upstreamCli; WebServer web(80); File blocklist; uint32_t numHashes = 0, totalBlocked = 0, totalAllowed = 0; uint8_t buf[1536]; // fits any non-fragmented UDP reply (EDNS answers can exceed 512) // first-level flash index (sorted sample hashes) + small direct-mapped cache static uint8_t blIndex[INDEX_ENTRIES][HASH_BYTES]; static uint8_t cacheKey[CACHE_SIZE][HASH_BYTES]; static uint8_t cacheRes[CACHE_SIZE]; static uint8_t cacheValid[CACHE_SIZE]; static uint8_t rangeBuf[MAX_RANGE * HASH_BYTES]; struct Dev { uint32_t ip; uint8_t mac[6]; uint32_t blocked, allowed, lastSeen; bool banned; String label; }; static const int MAX_CLIENTS = 96; Dev clients[MAX_CLIENTS]; int numClients = 0; static const int MAX_CUSTOM = 200; String customDom[MAX_CUSTOM]; uint64_t customHash[MAX_CUSTOM]; int numCustom = 0; static const int MAX_BAN = 32; uint32_t bannedIP[MAX_BAN]; int numBanned = 0; // remote blocklist auto-update String updateUrl = ""; // URL of a prebuilt blocklist.bin (e.g. GitHub release asset) uint32_t updateIntervalH = 24; // hours between auto-fetches uint32_t lastCheckMs = 0; String updateStatus = "never"; // WiFi provisioning (captive portal) Preferences prefs; DNSServer dnsPortal; String portalOpts; //