Привет, Хабр!
Три месяца назад я начал делать NextDNS-clone для Европы. Рекурсивный DNS с фильтрацией рекламы, трекеров и malware. Первый день: открываю Unbound, читаю man, всё понятно. К вечеру понимаю, что не подходит. Через неделю пишу свой резолвер на Go и вспоминаю поговорку про человека, который решил написать почтовый сервер. Никогда такого не было, и вот опять.
Сейчас в проде: 10 нод по миру, отвечает на DoH/DoT, фильтрует по миллионам доменов, RAM 60 МБ на ноду. Расскажу, почему ушёл от готового, что было больно, и где Unbound всё ещё быстрее. Спойлер: почти везде, но в наших условиях это не имеет значения.
В конце ссылки на open-source ядро резолвера и наш бесплатный тариф, можно потыкать.
Зачем вообще свой резолвер, если есть Unbound
Unbound — отличный софт. PowerDNS Recursor тоже. Я их обожаю как обожают старого кота: за то, что они есть и не требуют объяснений. В обычной ситуации просто бы поставил один из них и пошёл пить чай. Но у меня было три требования, которые ни тот, ни другой не закрывают штатно.
Миллионы DoH-эндпойнтов с разными правилами фильтрации
В NextDNS-like продукте каждый юзер получает свой config_id, короткий публичный токен в URL: https://dns.vantagedns.com/<config_id>/dns-query. У одного юзера может быть профиль «дом», «дети», «офис», у каждого свои блок-листы и whitelist. Это не «один резолвер с одним конфигом», это десятки тысяч логических резолверов на одной ноде.
В Unbound views есть, но они не масштабируются на десятки тысяч независимых наборов правил без боли с конфиг-генерацией и SIGHUP’ом каждые пять минут. Я как-то видел подобную конструкцию в одной телекомовской конторе в 2014-м. Там админ носил с собой бумажку с порядком действий, чтобы не забыть. Не хотелось в это ввязываться.
In-memory query log без записи на диск
Edge-нода не должна писать на диск ничего, что относится к юзеру. Это часть позиционирования. Поэтому query log живёт в кольцевом буфере в памяти, async-шипится в ClickHouse в Хельсинки, и всё. Unbound пишет логи в файл; для async-shipping надо городить tail+парсер.
Retention: 24 часа на free, до 30 дней на платных, дальше ClickHouse TTL чистит.
Bloom-фильтры для блок-листов с шарингом между профилями
У нас 10+ блок-листов от 50K до 5M доменов каждый. Если хранить их per-profile в виде set’ов, это сотни МБ RAM на ноду. С Bloom: 8 МБ на лист, шарится между всеми профилями, false-positive rate < 0.1%. В Unbound такого нет, там либо local-zone (медленно на больших списках), либо RPZ (тоже не совсем то).
Долго смотрел на варианты «допилить Unbound через модули» и отказался. Модульный API на C, миллионы конфигов через unix-сокет — это путь в ад дебага, причём сразу пятый круг. У меня нет на это ни нервов, ни жизни.
Почему Go, а не Rust/C++
Короткий ответ: я знаю Go и не знаю Rust на уровне «писать сетевую системную программу под нагрузкой». А C я в последний раз писал в университете, когда ещё думал, что segfault — это интересная диагностическая проблема. Сейчас я знаю, что это просто наказание за гордыню.
Длинный ответ:
Критерий |
Go |
Rust |
C/C++ |
|---|---|---|---|
Скорость старта |
✅ < 1 с |
✅ < 1 с |
✅ < 1 с |
Memory safety |
✅ GC |
✅ borrow check |
❌ |
Время до прототипа |
✅ дни |
⚠️ недели |
❌ месяцы |
Library: DNS |
✅ |
⚠️ |
✅ много |
Gorout./async |
✅ goroutine на запрос — норма |
⚠️ async runtime |
❌ epoll руками |
miekg/dns решал 80% задач из коробки: парсинг wire-format, сериализация, EDNS, DNSSEC. Я писал поверх неё recursive resolution, кеш, фильтрацию.
С Rust я бы выиграл 10-15% по latency и какие-то МБ по RAM. Но потерял бы 2 месяца на rewriting. Для бутстрап-проекта 1-человека это не выгодно.
Что было больно
Рекурсивная резолюция — это не просто «спросить .com NS, потом NS зоны»
Когда впервые пишешь recursive resolver, кажется: ну подумаешь, рекурсивно ходим по NS, кешируем, всё. Любой первокурсник напишет за вечер. Реальность, как водится, побила меня палкой по голове и обозвала наивным.
Параллельные NS lookups. У зоны обычно 2-4 авторитетных NS. Если последовательно дёргать каждый при таймауте, на холодном кеше один резолв example.com может занять 5+ секунд. Параллельные goroutines, race-to-first-response, отсев медленных.
Glue records. Когда запрашиваешь ns1.example.com для зоны example.com, это chicken-and-egg. Авторитетный сервер отдаёт A-запись для NS прямо в additional section. Если её игнорировать, рекурсия зацикливается. Был баг, где резолвер упирался в loop, потому что пропускал additional.
Negative caching. RFC 2308 (1998 год, между прочим, привет из эпохи, когда DNS ещё считался простым). Если NS вернул NXDOMAIN, надо кешировать с TTL из SOA, а не из ответа. Иначе при первом таймауте кешируем «домен не существует» на стандартный час и юзер ругается, а я смотрю в логи и не понимаю, почему GitHub лежит только у меня одного.
QNAME minimisation. Должно быть включено по умолчанию, это privacy. Запрашиваешь mail.google.com → у . спрашиваешь только com, у com только google.com, у google.com уже полный mail.google.com. Не «у всех NS на пути показываем full qname». Реализовать аккуратно отдельная история: некоторые корявые NS отвечают NODATA на минимизированный запрос, и ты сидишь, ловишь их в логах.
EDNS Client Subnet — отдельный ад
Без ECS Google отдаёт IP CDN из той страны, где стоит твоя нода. Юзер в Берлине через нашу ноду в Хельсинки получает финский IP googlevideo.com. Пинг 80 мс вместо 5 мс. YouTube тормозит, юзер пишет, что у нас «всё сломано».
С ECS публикуешь /24 подсеть юзера авторитетному NS. И вот тут начинается. Часть NS на ECS плюёт. Cloudflare принципиально не использует, потому что anycast. Часть отдаёт «правильный» IP. Часть выдаёт неправильный, а потом ты неделю выясняешь, почему. RFC 7871 формально опционален, и каждый сервер интерпретирует «опционально» по-своему, как бывшие супруги интерпретируют «давай останемся друзьями».
Тестирование на 30+ доменов руками было самым нудным этапом за все три месяца.
DNSSEC validation
Реализовать с нуля validation chain — две недели чистого времени. Я в итоге включил его опционально: для большинства юзеров он сейчас mostly cosmetic (95% доменов всё равно без подписи), а baggage серьёзный.
Cache poisoning — параноя 24/7
Любой кеширующий резолвер — потенциальная цель для cache poisoning. Source port randomization, txn-id randomization, 0x20 case randomization (да-да, тот самый трюк с регистром букв в qname, который выглядит как костыль и им и является), не доверять additional section без подтверждения через bailiwick. Это всё надо сделать до релиза, не после.
Раз 5 переписывал валидацию ответа. Каждый раз находил новый edge case и думал «ну вот теперь точно всё». Так не бывает. Запомните это, дети.
Цифры: что в итоге получилось
Замеры с одной ноды (Hetzner CPX21, 3 vCPU, 4 GB RAM, Helsinki):
QPS sustained: ~12 000 cached / ~3 500 cold P50 latency cached: 0.3 ms P50 latency cold (.com): 38 ms P99 latency cold: 180 ms RAM steady state: 62 MB Binary size: 14 MB (go build -ldflags="-s -w") Cold start to ready: 0.4 s
Для сравнения, Unbound на той же ноде с похожим конфигом:
QPS sustained: ~18 000 cached / ~5 000 cold P50 latency cached: 0.18 ms RAM steady state: 85 MB
Unbound быстрее на 30-40% по cached lookups. Это ожидаемо: он на C, код вылизан 20 лет. На холодных запросах разница меньше, потому что упираемся в сеть.
Для нашей нагрузки (~500 QPS на ноду пиково на текущем этапе) этой разницы не существует.
Что бы я сделал по-другому
Включил бы pprof и continuous profiling с первого дня. Первые два месяца дебажил allocations через runtime.ReadMemStats и top как пещерный человек. С pprof + Parca находил бы лики за минуты, а не за вечера. В оправдание скажу, что в три ночи кажется, будто top — это нормальный инструмент.
Не писал бы сразу Bloom-фильтры, взял бы суффиксные деревья. Bloom нужен на масштабе миллионов доменов. На старте у меня было 200K записей в листе, обычный suffix-trie дал бы exact matching без false positives, проще дебажить. Bloom добавил бы потом, когда уже припекает. Это, кстати, главное правило: не оптимизируй то, что не болит. Я его знаю с 2010 года и регулярно нарушаю.
Сделал бы feature flags с первого дня. Сейчас у меня 4 разных пути в коде «если ECS включён — так, иначе так», и они переплетены, как кабели за рабочим столом любого старого админа. С флагами можно было бы тестировать в проде на 1% трафика. Без них тестирую на себе, что довольно унизительно.
Open-source
Edge-резолвер — open source, MIT. Ссылка в конце статьи. Что НЕ open-source: control plane (биллинг, дашборд, blocklist-каталог) — это бизнес.
Зачем open-source edge: я хочу, чтобы юзер мог поднять свой собственный экземпляр и убедиться, что мы не врём про «не пишем на диск». В transparency report — статистика запросов на правоохранителей. Warrant canary обновляем еженедельно.
Что дальше
В следующих постах планирую разобрать конкретику:
как делал Bloom-фильтры с шарингом между профилями;
почему отказался от anycast в пользу geo-DNS на 10 PoPs и сколько это реально стоит;
как async-шипим query log в ClickHouse без потерь и без диска на edge.
Если интересно что-то конкретное — пишите в комментарии, постараюсь приоритизировать.
Ссылки:
Лендинг: https://vantagedns.com
Попробовать без регистрации, выдаём DNS-эндпойнт за 30 секунд: https://vantagedns.com/try
Free Pro на 90 дней для авторов Хабра: https://vantagedns.com/press-kit
Спасибо, что дочитали. Замечания и вопросы пишите в комменты.
Комментарии (8)

domix32
15.05.2026 08:06Что-то странное у вас с сайтом:
Предлагается попробовать сходу без регистрации и SMS - эти ссылки толком не открываются.
Предлагается посмотреть на исходный код - ссылки ведут на 404.

cyberscoper Автор
15.05.2026 08:06сервис на хостинге который заблокирован в рф по диапазонам ip, тут не помогу.
прикладываю ссылку на исходники нод. сайт я перебилдил теперь там везде актуальная ссылка на гитхаб
CyberScoper/vantagedns_edge

ANA_0x
15.05.2026 08:06Крутой проект, 10 нод и 60 МБ RAM — звучит чисто. Из любопытства глянул vantagedns.com — заголовки безопасности все на месте, HSTS, X-Frame-Options, CSP — приятно видеть. Единственное — source map открыт (
_nuxt/*.js.map), раскрывает исходники Nuxt-приложения. Для landing page не критично, но закрыть в nginx одной строчкой можно. В остальном — видно что человек не просто DNS пишет, а и вокруг тоже всё закрыто.
cyberscoper Автор
15.05.2026 08:06Благодарю) можете еще и app.vantagedns.com/ глянуть, я еще на днях провёл значительные улучшения по безопасности ну и конечно расписал это на странице в блоге я нашел там очень неприятные для себя моменты.. но благо я еще полноценно не стартанул
upd: Нет, source-maps НЕ раскрыты. я проверил оба фронта и это поведение SPA fallback в
/etc/nginx/sites-enabled/vantagedns.com:try_files $uri $uri/ /index.html;— любой несуществующий путь (включая*.map,/admin,/.env) отдаёт index.html со статусом 200. Реальных.js.mapNuxt в сборке не оставил, дайте знать если ошибся
ANA_0x
15.05.2026 08:06Это вам стоит выделить благодарство, ведь вы абсолютно правы, я был на 100 процентов уверен в инструменте, надо было перепроверить, (моя не осмотрительность) вручную посмотрев,
try_filesотдаётindex.htmlна любой несуществующий путь, реальных.mapфайлов в сборке нет. Мой инструмент ориентировался только на HTTP 200, не проверяя Content-Type и тело ответа — классический false positive на SPA fallback.Уже пофиксил логику: теперь валидирует Content-Type ≠
text/html, JSON-структуру и наличие"version":3перед тем как флагнуть. Так что спасибо!P.S. Пересканировал vantagedns.com — 100/100, чисто.
select26
Продукт интересный и написано здорово!
Не знаю как вы будуте конкурировать с тем же AdGuard, но желаю удачи!
На вашем сайте мне непонятно как выбрать ADS+trackers+Family safe. Мне такой провиль иногда нужен. Спиной чувствую, что FS уже включает ADS+trackers, но нигде подтверждения на сайте не нашел.
Еще раз желаю успеха!
p.s. если нужно, могу помочь GEO-есть опыт с GEO distributed DNS.
cyberscoper Автор
Благодарю, будет целая цепочка статей оставайтесь рядом!
Не уверен на протяжении первых лет .. двух? хоть о какой то конкуренции будет идти речь
На вашем сайте мне непонятно как выбрать ADS+trackers+Family safe. Мне такой провиль иногда нужен. - лучше всего войти в фрофиль там уже и создадите не по шаблону а включите все что вам небходимо)
А так у меня в FS, OISD basic EasyList OISD NSFW Hagezi gambling ну и конечно безопасный поиск по поисковикам. можете даже мне отписать выдам вам бета тест