Хабравчане, приветсвую!
Тема, которую сегодня разбираю, пришла ко мне, можно сказать, из ниоткуда и, наверное, прямой практической пользы не даёт. Но стало интересно докопаться до сути и заодно поделиться с вами.
По работе я веду SEO‑проекты, поэтому с DNS сталкиваюсь регулярно: переезды, новые домены, CDN, почта, странные редиректы и вечное «а почему сайт у меня уже работает, а у коллеги ещё нет». Обычно такие вопросы быстро уходят разработчикам или админам, а сам DNS остаётся где‑то в категории «ну, домен спросил адрес и получил ответ».
На днях как раз обсуждали с коллегами очередную проблему с DNS. Я полез чуть глубже и наткнулся на свежий доклад с IETF 126. Там в одном из экспериментов один DNS‑запрос породил 329 исходящих запросов. Сначала решил, что либо неправильно понял слайд, либо авторы специально собрали DNS‑матрёшку для конференции. Но оказалось, что всё гораздо интереснее.
В итоге полез разбираться, откуда берутся эти 329 запросов, и оказалось, что обычный DNS внутри устроен заметно веселее, чем выглядит из dig.
В DNS есть довольно уютная иллюзия.
Пишем:
dig example.com
Получаем IP.
В голове остается примерно такая схема:
я -> DNS -> 93.184.216.34
Максимум вспоминаются root‑сервер, .com, авторитетный сервер домена. Три‑четыре шага. Красиво.
А потом я увидел слайд с IETF 126.
На нем было одно число:
329.
Один PTR‑запрос. Один рекурсивный резолвер. Пустой кеш. BIND 9.18.
329 исходящих DNS‑запросов ради одного ответа.
Я сначала решил, что речь идет о каком‑то специально собранном DNS‑лабиринте. Оказалось интереснее: пример взят из вполне настоящего IPv6 reverse DNS.
И вот тут захотелось разобрать число 329 по косточкам. Потому что три запроса я еще готов был понять. 329 уже выглядит так, будто DNS решил воспользоваться интернетом по полной программе.
Сначала выбросим кеш
Главная причина, почему мы почти никогда всего этого цирка вокруг себя не замечаем, — кеш.
Рекурсивный DNS‑сервер постоянно помнит уже найденные NS, адреса этих NS, результаты предыдущих запросов, DNSSEC‑данные и делегации.
Поэтому обычный запрос выглядит скучно.
Для эксперимента нужен резолвер, который проснулся минуту назад и про интернет знает примерно следующее:
Вот список root-серверов. Удачи.
Именно такой режим использовался в измерениях Ondřej Surý из ISC: один BIND, один запрос, cold cache. В презентации отдельно указано, что все значения измерялись с холодным кешем.
Если хочется посмотреть механику самому, стенд собирается довольно просто.
Например, BIND:
sudo apt install bind9 dnsutils tcpdump
Слушаем его локально, включаем рекурсию и отправляем запрос прямо в свой resolver:
dig @127.0.0.1 example.com A
Перед новым прогоном очищаем кеш:
sudo rndc flush
А DNS‑трафик складываем в pcap:
sudo tcpdump -ni any port 53 -w dns.pcap
После запроса:
sudo pkill -INT tcpdump
Открываем dns.pcap в Wireshark.
Вот там DNS сразу перестает выглядеть как одна строчка из dig.
Самый скучный случай уже требует цепочки
Возьмем условный:
www.example.com
При действительно пустом состоянии резолверу приходится двигаться по дереву DNS.
Упрощенно:
. └── com └── example.com └── www.example.com
Сначала нужен root.
Потом серверы .com.
Потом серверы example.com.
Потом уже ответ для www.example.com.
Документация BIND описывает именно такую итерацию: при отсутствии нужной делегации в локальном кеше resolver идет к ближайшей известной точке дерева, вплоть до root, получает referral и двигается дальше.
Получается примерно:
1. root -> где .com? 2. .com -> где example.com? 3. example.com -> где www.example.com?
На этом месте хочется закрыть статью со словами «ну ладно, три запроса».
Рано.
Потому что в каждом referral лежат имена других DNS‑серверов.
А резолверу нужны их IP‑адреса.
И вот с этого момента дерево начинает расти в стороны.
DNS‑сервер тоже имеет доменное имя
Допустим, .org говорит:
example.org NS katelyn.ns.cloudflare.com example.org NS mitch.ns.cloudflare.com
Resolver получил названия серверов.
Для отправки UDP‑пакета название katelyn.ns.cloudflare.com почти бесполезно. Нужен адрес.
Значит внутри одного DNS‑разрешения появляется второе DNS‑разрешение:
Найди example.org | +-- сначала найди katelyn.ns.cloudflare.com
А cloudflare.com находится уже в .com.
То есть мы шли:
root -> org -> example.org
и внезапно прыгнули:
root -> com -> cloudflare.com -> katelyn.ns.cloudflare.com
Только после этого можем вернуться к исходному example.org.
Получается DNS‑рекурсия внутри DNS‑рекурсии.
В терминологии DNS это один из самых интересных случаев — out‑of‑bailiwick nameserver.
Сервер домена живет за пределами зоны, которую сейчас делегируют.
В презентации Surý пример разобран буквально по шагам:
Want: A example.org -> root -> .org -> example.org delegation NS = katelyn.ns.cloudflare.com -> root -> .com -> cloudflare.com -> katelyn.ns.cloudflare.com -> обратно к example.org
Один NS уже создал отдельную ветку.
А NS обычно несколько.
В какой‑то момент начинаешь подозревать, что DNS придумали люди, которым очень нравились квесты с побочными заданиями.
Glue спасает положение
Здесь DNS использует старый и очень практичный механизм — glue records.
Представим:
example.com NS ns1.example.com
Чтобы найти example.com, нужен ns1.example.com.
Чтобы найти ns1.example.com, сначала нужен example.com.
Красивый цикл.
Поэтому родительская зона может передать IP nameserver вместе с делегацией:
example.com. NS ns1.example.com. ns1.example.com. A 192.0.2.53
Вторая запись здесь работает как glue.
Resolver получает адрес сразу и продолжает путь.
У glue есть важная граница доверия. Современные резолверы осторожно относятся к адресам серверов, которые лежат за пределами соответствующей зоны. Такая осторожность напрямую связана с защитой кеша от подмены.
И тут появляется неприятная развилка для разработчика resolver:
Использовать больше полученных данных | +-> меньше запросов +-> больше поверхность для cache poisoning Перепроверять данные самостоятельно | +-> больше запросов +-> более строгая модель доверия
DNS начинает выглядеть уже не как дерево имен, а как дерево доверия.
Теперь добавим CNAME
А теперь берем обычный современный сервис.
Например:
dig A teams.microsoft.com
В разборе DNS‑тем с IETF 126 от APNIC цепочка выглядела так:
teams.microsoft.com CNAME teams.office.com teams.office.com CNAME tmc-g2.tm-4.office.com tmc-g2.tm-4.office.com CNAME teams-office-com.s-0005.dual-s-msedge.net teams-office-com.s-0005.dual-s-msedge.net CNAME s-0005.dual-s-msedge.net s-0005.dual-s-msedge.net A 52.123.129.14 A 52.123.128.14
Это уже четыре CNAME перед конечным A.
И каждый переход способен отправить resolver в новую зону.
microsoft.com | v office.com | v dual-s-msedge.net
А у каждой зоны свои NS.
У NS свои доменные имена.
Доменные имена NS снова требуют A/AAAA.
Те приводят к новым TLD и новым делегациям.
На бумаге CNAME выглядит как:
A -> B
Для cold‑cache resolver реальная картина ближе к такой:
A ├── NS зоны A │ ├── A NS1 │ ├── AAAA NS1 │ ├── A NS2 │ └── AAAA NS2 │ └── CNAME B ├── NS зоны B │ ├── A NS1 │ ├── AAAA NS1 │ └── ... │ └── CNAME C └── ...
Вот где количество запросов начинает расти очень быстро.
В измерениях с teams.microsoft.com BIND 9.18 делал от 141 до 180 запросов для A при выключенной DNSSEC‑валидации. Более новые ветки BIND тратили заметно меньше.
От одного dig.
180 DNS‑запросов.
И мы еще даже до IPv6 PTR из заголовка не дошли.
DNSSEC добавляет второе измерение
При DNSSEC resolverу мало получить запись.
Нужно проверить цепочку доверия.
В игре появляются:
DNSKEY DS RRSIG NSEC / NSEC3
Условно resolver спрашивает:
Какой IP у example.com?
Получает ответ.
Следующий вопрос:
А кто подтверждает, что этот ответ настоящий?
Для этого строится цепочка:
root trust anchor | DS v TLD | DS v example.com | DNSKEY | RRSIG
В официальном DNSSEC Guide BIND validating resolver описывается как рекурсивный сервер, выполняющий дополнительные действия для проверки подлинности полученных DNS‑данных.
С точки зрения безопасности это полезная работа.
С точки зрения нашего счетчика пакетов это еще несколько веток.
Особенно весело становится, когда зоны из основной цепочки имеют свои внешние NS, а те приводят в другие зоны.
И тут появляется PTR
Вот это место мне понравилось больше всего.
Прямой DNS выглядит естественно:
habr.com -> IP
Reverse DNS делает обратное:
IP -> hostname
Для IPv4 адрес преобразуется в дерево in-addr.arpa.
Например:
212.132.99.84
становится чем‑то вроде:
84.99.132.212.in-addr.arpa
С IPv6 веселее.
Адрес содержит 32 шестнадцатеричные цифры.
В reverse DNS каждая цифра превращается в отдельную DNS label, порядок разворачивается, а в конце добавляется:
ip6.arpa
Получается монстр вроде:
7.8.6.0.0.0.1.c.0.0.0.0.0.0.0.0. 2.2.0.0.8.e.2.0.c.7.6.0.1.0.0.2.ip6.arpa.
И это уже 32 уровня потенциального пространства делегирования.
Каждый участок может обслуживаться своей организацией.
Примерно так:
IANA | RIR | LIR | ISP | клиент
У каждого уровня могут быть отдельные NS.
Эти NS могут находиться в других доменах.
И начинается знакомое:
PTR | +-- delegation | +-- NS | +-- где находится этот NS? | +-- другая зона | +-- ее NS
Вот мы и приехали к 329.
Те самые 329
Использовался IPv6 PTR для dnssec-stats.ant.isi.edu.
С холодным кешем результаты получились такими:
BIND 9.18: 295-329 запросов BIND 9.20: 193-210 BIND 9.21: 102-128
То есть 329 — верхняя граница серии измерений на BIND 9.18, а не магическая константа DNS.
Это важнее самого красивого числа.
Одна и та же DNS‑конфигурация на разных поколениях resolver дала разницу примерно в три раза.
Протокол тот же.
Имя то же.
Ответ тот же.
Меняется стратегия resolver.
Почему BIND 9.21 тратит сильно меньше
Вот тут история из «забавного DNS‑факта» превращается в нормальную инженерную задачу.
Resolver постоянно выбирает, сколько данных подготовить заранее.
Есть два крайних подхода.
Вариант первый: разрешить всё
При каждой делегации получили пять NS:
ns1.provider-a.com ns2.provider-a.net ns3.provider-b.org ns4.provider-c.io ns5.provider-d.net
Можно сразу найти A и AAAA каждого.
Плюсы понятны: если первый сервер лежит, адреса остальных уже готовы.
Цена:
5 NS x A/AAAA x их собственные цепочки
Такой алгоритм прекрасно умеет превращать одно имя в сотни запросов.
Вариант второй: взять один NS
Получили пять серверов.
Выбрали один.
Разрешили только его.
Отправили запрос.
Работает быстро, пока выбранный NS отвечает.
При проблеме придется на ходу строить следующую ветку.
То есть у resolver есть настоящая оптимизационная задача:
сколько работы сделать заранее, чтобы сохранить устойчивость и одновременно ограничить DNS amplification внутри самой рекурсии.
В материалах IETF этот выбор сформулирован примерно как resolve everything против resolve bare minimum, а практическая стратегия лежит между ними.
В BIND 9.21.20 появился parent‑centric подход к обработке делегаций, который заметно уменьшил число cold‑cache запросов в приведенных тестах.
Результаты особенно красивые:
старый подход parent-centric google.com 24 7 facebook.com 25 7 chatgpt.com 32 8 x.com 100 41 reddit.com 51 29 bing.com 51 23 wikipedia.org 34 7
Разница огромная.
Причем x.com со своими 100 запросами здесь выглядит как человек, который зашел в магазин за хлебом и по дороге успел оформить ипотеку.
А зачем вообще считать DNS‑запросы
Поначалу 329 выглядит как спортивная статистика.
На практике у этой цифры есть несколько вполне физических последствий.
Каждый дополнительный запрос — это:
UDP/TCP пакет + работа resolver + состояние в памяти + ожидание ответа + возможный timeout + повторная попытка
А некоторые ветки идут последовательно.
Задержка одного сервера становится задержкой следующего шага.
Если один из внешних NS отвечает 800 мс, дерево начинает тормозить уже совсем по‑человечески.
В презентации есть хороший тезис: каждый такой переход на стороне resolver означает RTT. Кеш большую часть времени прячет эти расходы.
Именно поэтому DNS после рестарта resolver и DNS через пять минут его жизни — довольно разные системы.
Кеш здесь буквально держит интернет
После первого тяжелого разрешения большая часть промежуточных результатов остается в памяти:
NS для TLD адреса NS делегации DNSKEY DS A AAAA CNAME
Следующий клиент приходит с похожим запросом, а половина дерева уже собрана.
Вместо:
root -> TLD -> provider -> NS provider -> другая зона -> ...
получаем:
cache -> почти готово
Поэтому эксперимент с cold cache одновременно искусственный и очень полезный.
Он снимает крышку.
В обычной работе кеш закрывает сложность DNS настолько хорошо, что мы забываем о ее существовании.
Можно увидеть это самому
Самая простая лаборатория выглядит так:
sudo apt install bind9 dnsutils tcpdump
Очищаем кеш:
sudo rndc flush
Запускаем запись:
sudo tcpdump -ni any port 53 -w cold.pcap
Делаем один запрос:
dig @127.0.0.1 teams.microsoft.com A
Останавливаем capture.
Затем повторяем тот же dig, уже сохранив трафик в:
warm.pcap
Дальше Wireshark:
dns
или:
dns.flags.response == 0
Получаем только DNS queries.
Еще полезнее:
tshark -r cold.pcap \ -Y 'dns.flags.response == 0' \ -T fields \ -e ip.dst \ -e ipv6.dst \ -e dns.qry.name \ -e dns.qry.type
Можно отсортировать имена:
tshark -r cold.pcap \ -Y 'dns.flags.response == 0' \ -T fields \ -e dns.qry.name | sort | uniq -c | sort -nr
И вот здесь хорошо видно, насколько итоговый адрес скрывает происходящее внутри resolver.
Для чистого эксперимента я бы прогнал минимум:
example.com wikipedia.org teams.microsoft.com x.com какой-нибудь IPv4 PTR какой-нибудь IPv6 PTR
Каждый раз:
rndc flush capture один запрос stop
Потом повторить без rndc flush.
Получатся две совершенно разные картины одного DNS.
Самая интересная часть тут вообще про дизайн систем
До этого я воспринимал recursive resolver как довольно прямолинейную программу.
Получил имя.
Пошел сверху вниз.
Вернул адрес.
После этой истории модель поменялась.
Resolver постоянно решает задачи доверия и стоимости:
Какие glue принять? Какие адреса перепроверить? Сколько NS подготовить? Искать сразу IPv4 и IPv6? Как далеко раскрывать соседние зависимости? Когда использовать кеш? Сколько работы разрешить одному клиентскому запросу? Когда пора остановить рекурсию?
То есть DNS resolver больше похож на движок обхода графа с кешем, ограничениями и разными уровнями доверия.
Сам DNS тоже удобнее представлять графом.
Запрос:
teams.microsoft.com
легко тянет за собой:
microsoft.com azure-dns.org azure-dns.info azure-dns.com azure-dns.net office.com dual-s-msedge.net ...
И это только ради ответа на один вопрос.
Что я вынес из этих 329 запросов
Само число 329 — хороший заголовок.
Гораздо интереснее причины его появления.
DNS строился как распределенная система делегаций. Потом в него пришли CDN, managed DNS, длинные CNAME‑цепочки, DNSSEC, IPv6, независимые DNS‑провайдеры и более строгие правила работы с glue.
Каждая технология по отдельности выглядит вполне разумно.
Вместе они образуют граф зависимостей, который cold‑cache resolver должен раскрутить перед тем, как вернуть несколько байт ответа.
И лучший инженерный трюк DNS здесь очень старый:
кешировать всё, что еще имеет TTL.
Пока кеш теплый, сотни внутренних действий превращаются для клиента в обычный:
Query time: 1 msec
А после rndc flush крышка снова открывается.
И один DNS‑запрос внезапно оказывается далеко не одним запросом.
Материалы
За исходную точку я взял доклад Ondřej Surý (ISC) “How many DNS queries does it take to resolve one name?”, представленный на IETF 126 в Вене в июле 2026 года. Именно там приведены измерения cold‑cache BIND 9.18/9.20/9.21, включая диапазон 295–329 запросов для IPv6 PTR.
Механику рекурсивной итерации сверял с актуальной документацией BIND 9: resolver при отсутствии данных в кеше двигается от ближайшей известной делегации, получает referral и продолжает итерацию к более конкретной зоне.
Для части про DNSSEC использовал официальный DNSSEC Guide BIND: validating resolver выполняет дополнительную проверку подлинности DNS‑ответов и работает с DNSSEC‑цепочкой доверия.
Отдельный разбор DNS‑сессий IETF 126 опубликовал Geoff Huston из APNIC; там хорошо показаны out‑of‑bailiwick NS, glue, CNAME и причина роста числа рекурсивных запросов.

