DNS-резолюция — путь от example.com до IP
В предыдущих уроках мы посмотрели на иерархию DNS и на типы записей. Теперь сложим всё вместе и проследим, что физически происходит, когда твой компьютер делает запрос на example.com. Это удивительная цепочка действий, происходящая обычно за 10-50 миллисекунд.
В этом уроке:
- Прохождение запроса через stub resolver.
- Recursive vs iterative в деталях.
- Полный пошаговый разбор резолва
www.example.com. - Анализ через
dig +trace. - Что такое «cold» vs «warm» резолв.
- Дебаг типичных DNS-проблем.
Stub resolver — твой первый шаг
Когда ты в Python пишешь socket.gethostbyname("example.com"), или браузер открывает URL, или curl example.com — за работу берётся stub resolver. Это библиотечный код в твоей ОС, который:
- Читает конфигурацию (какой DNS-сервер использовать).
- Делает запрос.
- Возвращает ответ программе.
На Linux/macOS конфигурация:
cat /etc/resolv.conf
# Что-то вроде:
# nameserver 192.168.1.1 # DNS вашего роутера
# nameserver 8.8.8.8 # fallback
# search local
nameserver — это IP recursive resolver’а. search — суффиксы, которые добавляются к коротким именам (server -> server.local).
На macOS дополнительно:
scutil --dns | head -30
# Покажет DNS-конфигурацию по каждому интерфейсу (Wi-Fi, VPN, etc.)
На Windows:
ipconfig /all | findstr "DNS"
Stub resolver — это «упрощённый» DNS-клиент. Он не делает рекурсию сам — он отправляет один UDP-запрос (или несколько при ретраях) recursive resolver’у и доверяет ему сделать работу.
# Простейший stub-эквивалент на Python:
import socket
ip = socket.gethostbyname("example.com")
print(ip) # 93.184.216.34
# Это внутри идёт к настроенному в системе resolver'у и просит резолв
Кто делает работу: recursive resolver
Когда твой stub отправил запрос на 8.8.8.8 (или другой recursive resolver), он передал «горячую картошку». Recursive resolver теперь должен найти ответ.
Алгоритм:
1. Получить запрос: "www.example.com A?"
2. Проверить кэш:
- Если есть валидная (TTL не истёк) запись -- вернуть её.
- Если нет -- сделать рекурсивный обход иерархии.
3. Кэшировать ответ.
4. Вернуть клиенту.
Recursive resolver — основной игрок. Он:
- Знает root-серверы (hardcoded в его конфиге).
- Может ходить по иерархии.
- Поддерживает большой кэш.
- Обычно высокодоступен, низколатентен (geographically близок).
Популярные:
| Resolver | IP | Особенности |
|---|---|---|
| Google Public DNS | 8.8.8.8, 8.8.4.4 | Быстрый, geographically distributed |
| Cloudflare | 1.1.1.1, 1.0.0.1 | Privacy-focused (no logging) |
| Quad9 | 9.9.9.9 | Малware-фильтрация |
| OpenDNS | 208.67.222.222 | Cisco, parental control |
И, конечно, resolver’ы провайдеров и корпоративных сетей.
Полный путь резолва www.example.com
Допустим, твой компьютер хочет узнать IP www.example.com, и нигде нет кэша (cold start). Что происходит:
Видно, что resolver сделал 3 iterative запроса (root, TLD, authoritative) для одного запроса от клиента. На каждый шаг — 1 RTT. Если RTT 30 мс — total ~100 мс.
На «cold start» (ничего в кэше) — это типичные цифры. На «warm start» (что-то в кэше) — быстрее.
Что такое glue records
Заметь шаг 5: «NS для example.com — ns1.example.com, IP такой-то». Здесь есть тонкость. Если NS-серверы для example.com называются ns1.example.com и ns2.example.com, то resolver не может их резолвить отдельно — это chicken-and-egg: чтобы узнать IP ns1.example.com, нужно сначала найти example.com NS-серверы, которыми и являются… ns1.example.com.
Glue records решают это. На уровне родительской зоны (.com) хранятся не только NS-записи, но и A-записи самих NS-серверов:
example.com. NS ns1.example.com.
example.com. NS ns2.example.com.
ns1.example.com. A 1.2.3.4 <- glue
ns2.example.com. A 1.2.3.5 <- glue
Когда resolver получает referral от .com, он видит NS-имена + glue A-записи в additional section. Не нужно делать отдельный резолв.
# Посмотри glue records через trace:
dig +trace www.example.com 2>&1 | head -40
# В выводе для .com TLD ты увидишь NS-записи и параллельно A-записи NS-серверов
Recursive vs iterative — ещё раз, чтобы закрепить
Посмотри на диаграмму выше:
- Запрос от App -> Recursive resolver — recursive: «сделай работу за меня».
- Запросы от Recursive -> Root, TLD, Authoritative — iterative: «отвечай только тем, что у тебя есть».
Почему различие важно:
-
Root и TLD-серверы не делают рекурсию. Это защита от перегрузки и неправильного использования. Если бы они делали — кто-то мог бы использовать root как «DNS-прокси».
-
Recursive resolvers могут делать рекурсию. Это и есть их работа. Иногда они проверяют у клиента, разрешено ли (open vs closed resolver).
-
Open vs closed resolver. Open resolver — отвечает всем (8.8.8.8 такой). Closed — только своим клиентам (DNS провайдера твоего интернета).
# Заставить resolver сделать iterative запрос (без рекурсии):
dig @8.8.8.8 +norecurse example.com
# Если у resolver'а нет ответа в кэше -- он откажет или вернёт пустой ответ
# Это полезно для дебага: «есть ли ответ в кэше прямо сейчас?»
Кэширование на каждом уровне
Кэш — критическая часть DNS. Каждая запись имеет TTL (Time-To-Live), и resolver хранит её в кэше на этот срок.
Что и где кэшируется:
-
Stub resolver в ОС часто имеет свой маленький кэш. На macOS —
mDNSResponder, на systemd-linux —systemd-resolved. Это первый барьер. -
Browser cache. Chrome имеет свой DNS-кэш (
chrome://net-internals/#dns). Firefox тоже. Обычно minutes. -
Application cache. Многие приложения (особенно с long-running connections) кэшируют DNS внутри себя. Например, в Java по дефолту кэш «навсегда» — это часто причина проблем при смене IP сервера.
-
Recursive resolver cache (на 8.8.8.8 или у провайдера). Самый «большой» кэш, иногда минуты-часы.
-
Authoritative сервер не нуждается в кэше своих записей — он их хранит. Но он может кэшировать результаты child-zone NS.
# Очисти DNS-кэш на macOS:
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
# На Linux с systemd-resolved:
sudo systemd-resolve --flush-caches
# Очисти браузерный кэш Chrome:
# chrome://net-internals/#dns -> Clear host cache
# Замерь время резолва -- cold vs warm:
sudo dscacheutil -flushcache
time dig +short example.com # cold
time dig +short example.com # warm -- должен быть мгновенным
Цифровая практика: dig +trace
dig +trace имитирует iterative resolution из вашего компьютера. Это лучший способ увидеть полный путь:
dig +trace www.github.com
Что покажет (упрощённо):
. <- root section: 13 root-серверов
;; Received 525 bytes from 192.168.1.1#53 in 5 ms
com. <- referral from root: NS .com
;; Received 1206 bytes from 198.41.0.4#53(a.root-servers.net) in 23 ms
github.com. <- referral from .com: NS github.com
;; Received 690 bytes from 192.5.6.30#53(a.gtld-servers.net) in 25 ms
www.github.com. <- final answer from authoritative
;; Received 79 bytes from 205.251.193.165#53(ns-421.awsdns-52.com) in 30 ms
Каждый блок — это один iterative шаг. Видны IP-адреса серверов, RTT, и какие ответы получены.
Часто используется для дебага:
- Резолвится не там, где ожидалось —
dig +traceпокажет, где «свернул не туда». - Долгий резолв — какой шаг занимает много времени.
- Не работает на одном из серверов — какой NS сейчас не отвечает.
Cold vs warm — почему первый запрос медленнее
Если ты не открывал example.com за последний час, твой recursive resolver не имеет ничего в кэше. Резолв займёт ~100 мс (3 RTT по 30 мс).
Если ты открывал something.com минуту назад, у resolver’а в кэше:
- NS для .com (TTL 2 дня — почти всегда в кэше).
- NS для something.com (TTL ~1 день).
- A для something.com (TTL ~5 минут).
Поэтому если запрашиваешь снова something.com, ответ из кэша — мгновенный. Если запрашиваешь other.com (того же TLD .com), пропускаешь шаг к root, шаг к .com — нужен только шаг к authoritative other.com. Это ~30 мс.
# Замерь cold vs warm:
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
# Cold (никаких .com доменов в кэше):
time dig +short cnn.com
# Warm (.com уже в кэше):
time dig +short bbc.com # быстрее
time dig +short github.com # ещё быстрее (если .com NS закэшированы)
# Очень warm (этот домен уже в кэше):
time dig +short github.com # мгновенно
Что может пойти не так
Типичные DNS-проблемы:
1. NXDOMAIN — домен не существует
dig nonexistent-domain-xyz.com
# В выводе: ';; status: NXDOMAIN'
# = no such domain
Может означать:
- Домен реально не зарегистрирован.
- DNS-провайдер сломан и не отвечает.
- Ты опечатался в имени.
2. SERVFAIL — что-то сломалось
;; status: SERVFAIL
Resolver не смог получить ответ. Причины:
- Authoritative-сервер не ответил.
- DNSSEC validation failed.
- Сетевой timeout.
3. Refused
;; status: REFUSED
Сервер отказался отвечать. Обычно:
- Ты обращаешься к closed resolver, который не обслуживает твой IP.
- На authoritative-сервере спрашиваешь зону, за которую он не отвечает.
4. Stale cache
Сменил IP сервера, но клиенты всё ещё видят старый IP. Причина — кэш TTL ещё не истёк. Решение — подождать или очистить кэш на стороне клиента.
5. DNS hijacking
Кто-то на пути (часто провайдер или Wi-Fi сети) подменяет DNS-ответы. Защита — DoH/DoT (об этом в уроке 5).
dig, nslookup, ss — диагностика DNS и соединений на LinuxПопробуй сам
# 1. Полный trace одного домена:
dig +trace www.example.com 2>&1 | less
# 2. Замерь cold vs warm резолв:
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder # macOS
# Linux: sudo systemd-resolve --flush-caches
time dig +short cnn.com # cold
time dig +short cnn.com # warm
# 3. Что в кэше у твоего стаб-resolver'а:
# macOS:
sudo killall -INFO mDNSResponder
sudo log show --info --predicate 'subsystem == "com.apple.mDNSResponder"' --last 1m | tail -30
# Linux systemd-resolved:
resolvectl statistics
resolvectl show-cache # not always available
# 4. Дебаг через альтернативные resolvers:
dig @8.8.8.8 example.com
dig @1.1.1.1 example.com
dig @9.9.9.9 example.com
# Сравни ответы. Иногда они отличаются (CDN-оптимизация)
# 5. Дай stub resolver'у задачу:
python3 -c "import socket; print(socket.gethostbyname('example.com'))"
python3 -c "import socket; print(socket.getaddrinfo('example.com', 80))"
# Второй -- более подробно
# 6. Симулируй полный путь вручную:
# Спроси у root:
dig @a.root-servers.net example.com NS +noall +authority
# Возьми один из .com NS и спроси его:
dig @a.gtld-servers.net example.com NS +noall +authority
# Возьми один из example.com NS и спроси его:
dig @ns1.example.com example.com A