ESP32-C3 Adblock: DNS-блокировщик рекламы на чипе за $2 без PSRAM

На GitHub опубликован проект esp32-c3-adblock: DNS-блокировщик рекламы в стиле Pi-hole, который работает на чипе ESP32-C3 стоимостью около $2 и не требует PSRAM (внешней оперативной памяти). В README проект упомянут как попавший в материалы Tom's Hardware, XDA Developers и Korben; содержание этих публикаций в источнике не приводится.
Главная идея, по словам README: список блокировки не нужно держать в оперативной памяти. Большинство ESP32-блокировщиков загружают домены строками в RAM, поэтому им нужна PSRAM. Здесь домены хранятся как отсортированные 40-битные (5 байт) хэши во флеш-памяти, и поиск идёт бинарным поиском. По заявлению README, 140 000+ доменов умещаются примерно в 0,7 МБ флеш-памяти, сопоставляются за ~10 мс и занимают ~50 КБ оперативной памяти.
Как это работает: устройство получает DNS-запрос, извлекает домен, считает хэш FNV-1a (включая родительские суффиксы) и ищет его в таблице во флеш-памяти. Если хэш найден, устройство отвечает 0.0.0.0 (домен заблокирован); если нет, запрос пересылается вышестоящему резолверу, а ответ передаётся клиенту. Заблокированный домен блокирует и свои поддомены.
Почему именно 40 бит. Число коллизий следует парадоксу дней рождения: при 141 тыс. доменов ожидается ~0 коллизий, при 537 тыс. около 1 (то есть один незадачливый домен будет заблокирован по ошибке). Переход на 32 бита сэкономил бы 20% флеш-памяти, но стоил бы ~7 коллизий при 250 тыс. доменов; 64 бита тратят лишние 3 байта на домен ради проблемы, которой нет. Авторы подчёркивают, что приём не привязан к C3: на ESP32-S3 с 16 МБ флеш-памяти такие хэши вмещают ~2,7 млн доменов против ~466 тыс. строк в 8 МБ PSRAM.
Аппаратура и сборка. Протестировано на плате C3 SuperMini (4 МБ флеш-памяти, PSRAM не нужна); сборка для классического ESP32 (DevKit/WROOM, 4 МБ) добавлена сообществом и только компилируется, но не проверялась на устройстве. Питать лучше от стабильного USB-источника, например от USB-порта роутера: дешёвые переходники могут вызывать просадку питания радиомодуля. В репозитории есть STL-файл корпуса для печати на 3D-принтере (слои 0,2 мм, заполнение ~15%; антенну не закрывать, вентиляцию оставить, плата в простое нагревается до 45-55 °C). Требуется свежий PlatformIO: версия из дистрибутивов (например 4.3.4) слишком старая и не собирается.
Настройка. Нужно скопировать шаблон secrets.h и задать WEB_USER, WEB_PASS и OTA_PASS (они обязательны), затем собрать список скриптом tools/build_blocklist.py и прошить устройство один раз по USB (pio run -t upload и uploadfs). Скрипт принимает hosts-файлы, простые списки доменов и базовые правила AdGuard/Adblock; правила, которые нельзя выразить хэш-списком (регулярные выражения, шаблоны, модификаторы), пропускаются и подсчитываются. По умолчанию список собирается из StevenBlack base и Hagezi Light, ~100 тыс. записей. Дальше прошивка и список обновляются по WiFi: каждый понедельник GitHub Actions пересобирает свежий список, и устройство может забирать его по постоянной ссылке по расписанию. Если подключиться к сети не удалось, запускается открытая точка доступа C3-AdBlock-XXXX с порталом настройки WiFi. Веб-панель доступна по адресу c3adblock.local, поддерживается mDNS.
Компромисс на 4 МБ флеш-памяти: обновление прошивки по воздуху требует двух слотов приложения, и для списка остаётся ~1,3 МБ (максимум ~250 тыс. доменов). Агрессивный список на 537 тыс. доменов помещается только в схему с одним слотом, то есть без OTA-обновления прошивки. Выбор делается в partitions.csv.
В дорожной карте: сегментированный префиксный индекс, который сократит ~18 чтений флеш-памяти на запрос до ~1-2 (issue #3) и даст выигрыш по пропускной способности, а также режим DHCP-сервера для полностью автоматической настройки. Лицензия MIT.
Ключевые факты
- Проект esp32-c3-adblock запускает DNS-блокировщик рекламы в стиле Pi-hole на чипе ESP32-C3 за ~$2 без PSRAM.
- Вместо строк в RAM домены хранятся как отсортированные 40-битные хэши во флеш-памяти: 140 000+ доменов занимают ~0,7 МБ, поиск ~10 мс, расход RAM ~50 КБ (по заявлению README).
- 40 бит выбраны как баланс: при 141 тыс. доменов ~0 коллизий, при 537 тыс. около 1; 32 бита сэкономили бы 20% места, но дали бы ~7 коллизий при 250 тыс.
- На 4 МБ флеш-памяти с OTA-обновлением прошивки помещается максимум ~250 тыс. доменов; список на 537 тыс. требует схемы без OTA.
- Панель управления работает по обычному HTTP; изменяющие запросы защищены Basic Auth, точка WiFi-настройки открыта по замыслу.
Почему это важно
Проект показывает, как обойти ограничение по оперативной памяти на самом дешёвом оборудовании: хранить во флеш-памяти не строки, а короткие хэши фиксированного размера и искать по ним бинарным поиском. По данным README, на ESP32-S3 с 16 МБ такие хэши вмещают ~2,7 млн доменов против ~466 тыс. строк в 8 МБ PSRAM, и авторы считают, что хэши во флеш-памяти выигрывают у строк в PSRAM «практически везде». Это утверждение самого README, независимых проверок в источнике нет.
Кому это важно
Тем, кто хочет домашний DNS-фильтр рекламы и трекеров за пару долларов, и разработчикам встраиваемых систем, которым интересен приём с хэш-таблицей во флеш-памяти для микроконтроллеров без внешней RAM. К ИИ и машинному обучению проект отношения не имеет.
Как это применить
Нужна любая плата на ESP32-C3 с 4 МБ флеш-памяти (проверена C3 SuperMini). Шаги: скопировать src/secrets.example.h в src/secrets.h и задать WEB_USER, WEB_PASS, OTA_PASS; собрать список командой python3 tools/build_blocklist.py data/blocklist.bin; прошить pio run -t upload и pio run -t uploadfs. Затем указать IP устройства как DNS-сервер (или вторичный резолвер за основным). Проверка: dig @<ip-устройства> doubleclick.net должен вернуть 0.0.0.0, github.com, настоящий адрес. Дальнейшие обновления идут по WiFi, список можно подтягивать по постоянной ссылке из релизов репозитория. Лицензия MIT. Однокнопочный установщик в браузере анонсирован, но размещение ещё не решено.
Можно ли доверять
Все цифры (140 000+ доменов в ~0,7 МБ, ~10 мс, ~50 КБ RAM) взяты из README самого проекта. Методика замеров не описана, и не сказано, среднее ли это время или худший случай. Протестировано только на C3 SuperMini; сборка для классического ESP32 только компилируется. Упоминания Tom's Hardware, XDA Developers и Korben даны со слов README. Автор или сопровождающий в тексте README не назван. Расчёт коллизий опирается на стандартную оценку по парадоксу дней рождения.
Риски и подводные камни
Из-за 40-битных хэшей возможны редкие ложные блокировки: при 537 тыс. доменов ожидается около одной коллизии. Панель работает только по HTTP на порту 80: у чипа нет запаса на TLS-сервер, Basic Auth не шифрует учётные данные, и тот, кто прослушивает вашу сеть, может их прочитать. Точка доступа для первичной настройки WiFi открыта намеренно, и пароль, введённый в портале, защищён лишь коротким окном настройки. Если оставить значения-заглушки из secrets.example.h, пароль будет публично известен: прошивка предупредит в журнале и на панели, но всё равно запустится. Для изменяющих запросов нужен заголовок X-Requested-With: c3-adblock, поэтому /forgetwifi больше нельзя открыть простой ссылкой. Кроме того, при 4 МБ флеш-памяти приходится выбирать между OTA-обновлением прошивки и самым большим списком, а скорость сейчас ограничена: индекс, снижающий чтения флеш-памяти, пока не сделан.
«Хэши во флеш-памяти бьют строки в PSRAM практически везде.»
— README проекта esp32-c3-adblock