Меню
Меню
Главная
RU

AdGuard DNS 2.0 переходит на open source

AdGuard DNS всегда был с открытым кодом в соответствии с нашей политикой (открываем код у всех бесплатных продуктов).

Но с выходом версии 2.0 мы столкнулись с проблемой: с одной стороны, AdGuard DNS предоставляет бесплатный публичный сервер, а с другой, содержит логику персонального сервера. Мы долго размышляли о том, что делать, и в итоге всё-таки решили открыть код, но сделать это под достаточно строгой лицензией (AGPL).

Зачем мы это делаем? У этого решения как минимум две причины.

Во-первых, нам важно, чтобы пользователи AdGuard DNS нам доверяли и видели, что именно делает сервер. Мало просто сказать, что мы за всё хорошее и против всего плохого — у сообщества должна быть возможность проверить это и спросить с нас.

Во-вторых, нам кажется, что мы проделали хорошую работу и нам хотелось бы поделиться её результатами с сообществом разработчиков.

Здесь можно было бы просто дать ссылку на репозиторий, но это будет скучно и неинтересно. Поэтому сегодня я вам расскажу историю того, как менялся код AdGuard DNS, чем это было обусловлено и почему мы принимали те или иные архитектурные решения.

Что представляет AdGuard DNS из себя сегодня:

  1. Он развёрнут на десятках серверов по всему миру
  2. Обрабатывает больше миллиона DNS-запросов в секунду от более чем 50 миллионов пользователей
  3. Более 90% этих запросов — это зашифрованные протоколы (DNS-over-TLS, DNS-over-HTTPS, DNS-over-QUIC).

По цифрам понятно, что это очень приличная нагрузка, и мне кажется, мы неплохо с этим справились. Но когда мы только начинали, всё было совсем не так радужно.

Внимание: это очень технический пост, рассчитанный на инженеров и им симпатизирующих. Будьте готовы.

С чего всё начиналось

Шёл далекий 2016 год. Блокировщик рекламы AdGuard уже был запущен на всех платформах, где его можно было запустить, но у него до сих пор оставались белые пятна. А благодаря росту IOT эти белые пятна стремительно разрастались. Как же добраться до них так, чтобы это было доступно обычному пользователю?

В тот момент мы знали о DNS, что он должен отвечать на вопрос о том, какой IP-адрес у того или иного домена. И нам всё равно казалось, что чтобы блокировать контент рекламу везде, а не только на устройствах, где установлен AdGuard, DNS был самым простым решением. Настроить его не так сложно: прописываешь себе в настройки роутера четыре циферки — и вуаля, вся сеть защищена. Так что, когда мы решились сделать свой сервис, казалось, что дело за малым: запустить DNS-сервис и сделать его публичным.

Задача казалась просто элементарной:

  • Наш сервер принимает DNS-запрос
  • В зависимости от того, какой домен запрошен, мы делаем одно из двух:
    • Либо проксируем домен дальше на так называемый «рекурсивный DNS-сервер» (в первые недели мы вообще проксировали на Google DNS, только позже подняв свой отдельный инстанс).
    • Либо сразу же отвечаем, что такого домена не существует. В этом случае, у клиента соединиться с доменом не получится и задача блокировки будет выполнена.

Времени на всё про всё не было, так что мы взяли те инструменты, которыми умели пользоваться. В те времена AdGuard для Android был написан в основном на Java, так что мы решили переиспользовать часть кода и DNS-сервер тоже написать на Java.

Первая бета-версия на Java

Слушаем входящие UDP-пакеты на 53 порту и отправляем их обрабатываться в отдельном потоке:

Слушаем

Парсим входящие запросы и либо передаём тот же самый UDP пакет дальше, либо возвращаем заблокированный ответ:

Парсим

Помнится, мы были очень довольны, что весь процесс занял от силы пару недель. Но при подготовке к этому посту я откопал старый код, и, честно вам признаюсь, количество чисто функциональных ошибок, допущенных при его написании, бъёт все рекорды.

Вот, например, одна из них: сервер слушал только UDP. В случае DNS так делать категорически нельзя. Дело в том, что UDP ограничивает возможный размер DNS-ответа. Если этот ответ получается больше определённой величины (например, больше 1252 байт), то сервер вернет клиенту «обрезанный» ответ, что является сигналом для использования TCP для получения полного ответа. Да только как бы клиент его использовал!

Правила DNS-фильтрации

В самом начале работы над AdGuard DNS мы приняли одно хорошее решение, которому я рад до сих пор. Исторически DNS-блокировка контента основывалось на использовании огромных файлов HOSTS — простых списков доменных имён, которые нужно заблокировать. Но на уровне DNS можно сделать очень много вещей, помимо блокировки, и нужен был способ как-то выразить эти более сложные правила. Мы решили принять знакомый синтаксис блокировщика рекламы для списков блокировки DNS и расширить его возможностями, присущими только DNS.

Вот несколько примеров того, что можно сделать с его помощью (обратите внимание, что этот синтаксис применяется во всех продуктах AdGuard, включая AdGuard Home).

  • ||example.org^$client=MyMac блокирует каждый запрос с устройства MyMac
  • ||example.org^$dnsrewrite=example.net перенаправляет example.org на example.net
  • *$denyallow=com|net блокирует всё, кроме доменов *.com и *.net
  • *$dnstype=AAAA избавляет от IPv6

Больше о синтаксисе правил DNS-фильтрации можно узнать в нашей Базе знаний.

AdGuard DNS 1.0

Но даже несмотря на эти идиотские ошибки, AdGuard DNS начал пользоваться популярностью и мы стали выделять на него всё больше времени. Пока в один из дней не решили переписать его полностью.

Так был рождён AdGuard DNS 1.0. В качестве основы мы взяли CoreDNS, расширяемый DNS-сервер, написанный на языке Go. Мы выбрали CoreDNS из-за гибкого механизма плагинов, который позволил легко расширить его функциональность и добавить фильтрацию, ради которой и используют AdGuard DNS. Для этого мы переписали движок фильтрации на Go (естественно, его код также открыт и опубликован на Github).

Но достаточно быстро мы столкнулись с проблемой дальнейшего расширения CoreDNS. Несмотря на гибкость механизма плагинов, далеко не всё можно было реализовать с его помощью. Чтобы ускорить работу DNS-over-TLS и добавить нативную поддержку DNSCrypt и DNS-over-QUIC, мы стали поддерживать собственный форк CoreDNS, но чем дальше, тем сложнее было синхронизировать его с апстримом. Поэтому при разработке новой версии AdGuard DNS мы решили писать реализацию DNS-сервера с нуля.

AdGuard DNS 2.0

AdGuard DNS 2.0 значительно расширяет возможности пользователей по сравнению с обычным DNS-сервером. Он позволяет пользователям самим управлять фильтрацией и следить за тем, что делают их устройства.

Статистика

А это привело к тому, что архитектурно сервис стал гораздо сложнее. Очень примерная схема текущей конфигурации приведена на диаграмме ниже (она включает только необходимое):

Схема
Из чего состоит AdGuard DNS 2.0

  • DNS Server — это инстансы собственно DNS-сервера, которые отвечают за обработку запросов и их фильтрацию. Что они делают помимо этого:
  • Периодически запрашивают обновления конфигурации клиентов у API Server
  • Для зарегистрированных клиентов, у которых включено логирование, они пишут лог запросов на диск
  • Лог запросов с помощью Filebeat и Logstash оперативно попадает в центральную аналитическую базу.
  • Для хранения логов и построению отчетов по ним мы используем Clickhouse. По нашему мнению, это лучшая из доступных аналитических баз данных.
  • Web Server — это сайт AdGuard DNS. Web Server не общается напрямую с базами данных, а делает это через API Server.
  • API Server предоставляет другим сервисам и пользователям API, через который можно управлять настройками, а также получать доступ к информации из баз данных.

Сегодня мы публикуем код главной части AdGuard DNS — самого DNS сервера. Вы можете ознакомиться с ним на Github.

Часть этого кода мы планируем в будущем использовать в наших других проектах с открытым исходным кодом (AdGuard Home и dnsproxy) и открыть под более свободной лицензией.

Понравился пост?