Что показывает схема
Процесс начинает не с IP-пакета, а с вызова getaddrinfo(). Он передаёт имя и service системному resolver, а получает список адресных структур, пригодных для socket() и connect(). На этом шаге TCP-соединения ещё нет. Ошибка разрешения имени поэтому не является TCP timeout, а успешный DNS-ответ ещё не доказывает доступность порта.
Схема показывает распространённый путь через stub resolver и recursive resolver. Это не единственно возможный источник результата: локальная политика, hosts file, корпоративный namespace и уже заполненные caches тоже могут повлиять на ответ. Важная мысль остаётся той же: приложение обращается к системному интерфейсу разрешения имени, а не обязано само обходить root, TLD и authoritative servers.
Как читать компоненты
- Процесс и stub resolver. getaddrinfo() — синхронная для вызывающего кода граница системного API. Сетевые DNS-запросы обычно выполняет не прикладной parser, а resolver infrastructure ОС.
- Recursive resolver. Принимает рекурсивный запрос и возвращает конечный ответ или ошибку. При cache miss он может выполнять последовательность итеративных запросов.
- Positive / negative cache. Ключ включает как минимум имя, class и record type. A и AAAA — разные типы resource records; «получить оба» не означает, что они обязаны лежать в одном DNS message.
- Root, TLD, authoritative. Root и TLD обычно дают referrals. Авторитетный сервер зоны даёт ответ о данных зоны или подтверждает отсутствие имени.
Три сценария
- Cache hit. Recursive resolver отвечает без обхода иерархии. TTL ограничивает срок, в течение которого это доказательство можно переиспользовать.
- Cache miss. Resolver идёт от root к TLD и authoritative server, кеширует полученный record и возвращает результат stub resolver.
- NXDOMAIN. Отрицательный authoritative результат отделён от timeout и SERVFAIL. Он тоже может быть закеширован, поэтому исправление зоны не обязано мгновенно появиться у каждого клиента.
Предсказание до запуска
Перед анимацией ответьте: если getaddrinfo() вернул два IPv6 и два IPv4 адреса, какой именно адрес «является адресом сайта»? Правильный ответ — ни один заранее. Приложение получило набор кандидатов и ещё должно выбрать порядок попыток соединения. Следующая схема делает этот переход явным.
Практика на C и Winsock
Для нового dual-stack кода используйте getaddrinfo() с AF_UNSPEC, SOCK_STREAM и нужным service. Проверяйте код возврата отдельно от WSAGetLastError(): getaddrinfo() возвращает собственные EAI_* ошибки. Каждый элемент связного списка addrinfo содержит готовые address family, socket type, protocol и sockaddr; освобождайте весь список через freeaddrinfo().
Не копируйте только первый sockaddr и не считайте задачу завершённой. Сохраните список кандидатов, затем для каждой попытки создавайте socket с параметрами именно этого addrinfo. Диагностический лог должен фиксировать исходное имя, record families, выбранный адрес и ошибку конкретной connect-попытки, но не должен путать DNS failure с отказом порта.
Evidence: что собирать
- точное имя, service, время и host, где выполнен lookup;
- коды getaddrinfo(), полученные families и адреса;
- DNS answer с qname, qtype, rcode и TTL;
- различие между cache hit, authoritative NXDOMAIN и отсутствием ответа;
- только после этого — evidence по connect() для каждого кандидата.
Границы модели и частые ошибки
- Успешный A/AAAA answer не доказывает, что server process слушает порт.
- NXDOMAIN, NODATA, SERVFAIL и timeout — разные исходы; нельзя сворачивать их в строку «DNS сломан».
- TTL — срок допустимого кеширования record, а не обещание, что приложение будет держать соединение столько же.
- Recursive resolver не обязан каждый раз обращаться ко всем трём уровням: caches и referrals сокращают путь.
Контрольные вопросы
- Почему packet capture без DNS-трафика не доказывает, что приложение не разрешало имя?
- Что именно становится устаревшим после истечения TTL?
- На каком шаге появляется socket handle: до или после выбора адресного кандидата?
Источники