- PVSM.RU - https://www.pvsm.ru -
читатели, приветсвую!
Тема, которую сегодня разбираю, пришла ко мне, можно сказать, из ниоткуда и, наверное, прямой практической пользы не даёт. Но стало интересно докопаться до сути и заодно поделиться с вами.
По работе я веду 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 [1].
На нем было одно число:
329.
Один PTR‑запрос. Один рекурсивный резолвер. Пустой кеш. BIND 9.18.
329 исходящих DNS‑запросов ради одного ответа.
Я сначала решил, что речь идет о каком‑то специально собранном DNS‑лабиринте. Оказалось интереснее: пример взят из вполне настоящего IPv6 reverse DNS.
И вот тут захотелось разобрать число 329 по косточкам. Потому что три запроса я еще готов был понять. 329 уже выглядит так, будто DNS решил воспользоваться интернетом по полной программе.
Главная причина, почему мы почти никогда всего этого цирка вокруг себя не замечаем, — кеш.
Рекурсивный DNS‑сервер постоянно помнит уже найденные NS, адреса этих NS, результаты предыдущих запросов, DNSSEC‑данные и делегации.
Поэтому обычный запрос выглядит скучно.
Для эксперимента нужен резолвер, который проснулся минуту назад и про интернет знает примерно следующее:
Вот список root-серверов.
Удачи.
Именно такой режим использовался в измерениях Ondřej Surý из ISC [1]: один 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 [2] описывает именно такую итерацию: при отсутствии нужной делегации в локальном кеше resolver идет к ближайшей известной точке дерева, вплоть до root, получает referral и двигается дальше.
Получается примерно:
1. root -> где .com?
2. .com -> где example.com?
3. example.com -> где www.example.com?
На этом месте хочется закрыть статью со словами «ну ладно, три запроса».
Рано.
Потому что в каждом referral лежат имена других DNS‑серверов.
А резолверу нужны их IP‑адреса.
И вот с этого момента дерево начинает расти в стороны.
Допустим, .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ý [1] пример разобран буквально по шагам:
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 придумали люди, которым очень нравились квесты с побочными заданиями.
Здесь 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 начинает выглядеть уже не как дерево имен, а как дерево доверия.
А теперь берем обычный современный сервис.
Например:
dig A teams.microsoft.com
В разборе DNS‑тем с IETF 126 от APNIC [3] цепочка выглядела так:
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 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 [4] validating resolver описывается как рекурсивный сервер, выполняющий дополнительные действия для проверки подлинности полученных DNS‑данных.
С точки зрения безопасности это полезная работа.
С точки зрения нашего счетчика пакетов это еще несколько веток.
Особенно весело становится, когда зоны из основной цепочки имеют свои внешние NS, а те приводят в другие зоны.
Вот это место мне понравилось больше всего.
Прямой 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.
Использовался 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.
Вот тут история из «забавного 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 отвечает.
При проблеме придется на ходу строить следующую ветку.
То есть у 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 запросами здесь выглядит как человек, который зашел в магазин за хлебом и по дороге успел оформить ипотеку.
Поначалу 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 — хороший заголовок.
Гораздо интереснее причины его появления.
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?” [1], представленный на IETF 126 в Вене в июле 2026 года. Именно там приведены измерения cold‑cache BIND 9.18/9.20/9.21, включая диапазон 295–329 запросов для IPv6 PTR.
Механику рекурсивной итерации сверял с актуальной документацией BIND 9 [2]: resolver при отсутствии данных в кеше двигается от ближайшей известной делегации, получает referral и продолжает итерацию к более конкретной зоне.
Для части про DNSSEC использовал официальный DNSSEC Guide BIND [4]: validating resolver выполняет дополнительную проверку подлинности DNS‑ответов и работает с DNSSEC‑цепочкой доверия.
Отдельный разбор DNS‑сессий IETF 126 [3] опубликовал Geoff Huston из APNIC; там хорошо показаны out‑of‑bailiwick NS, glue, CNAME и причина роста числа рекурсивных запросов.
Автор: moscow_city
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/bind/456546
Ссылки в тексте:
[1] IETF 126: https://www.iepg.org/2026-07-19-ietf126/slides-126-iepg-sessa-01-dns-transitive-trust-and-how-many-is-many-00.pdf
[2] Документация BIND: https://bind9.readthedocs.io/en/stable/chapter6.html
[3] разборе DNS‑тем с IETF 126 от APNIC: https://blog.apnic.net/2026/07/29/dns-topics-at-ietf-126/
[4] официальном DNSSEC Guide BIND: https://bind9.readthedocs.io/en/stable/dnssec-guide.html
[5] Источник: https://habr.com/ru/articles/1070280/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1070280
Нажмите здесь для печати.