> По какому протоколу работает DNS (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: VK

Стек: Go

> Пример ответа

Короткий ответ

DNS работает преимущественно по протоколу UDP на порту 53, с fallback на TCP для больших ответов, zone transfer и DNSSEC. Запросы и ответы используют единый формат сообщений, определённый в RFC 1035. UDP обеспечивает низкую задержку и лёгкость, TCP - надёжность для ответов, превышающих 512 байт (или 1232 байта с учётом современных рекомендаций по EDNS0).

Подробное объяснение

DNS использует два транспортных протокола:

  • UDP (порт 53) - основной транспорт для обычных запросов. Это stateless протокол: клиент отправляет запрос, сервер отвечает одним датаграммой. UDP не требует установки соединения, поэтому latency минимальна. Ретрансмиссию на уровне приложения выполняет сам DNS-резолвер (обычно через таймаут в 1-5 секунд).

  • TCP (порт 53) - используется в следующих случаях:

    • Ответ превышает максимальный размер UDP-датаграммы (исторически 512 байт, с EDNS0 - до 4096, современная рекомендация - 1232 байта для избежания IP fragmentation).
    • Zone transfer (AXFR/IXFR) между первичным и вторичным DNS-серверами.
    • DNSSEC, когда ответ содержит много RRsets и не помещается в UDP.
    • Когда сервер явно указывает в ответе флаг TC (truncated) - клиент обязан повторить запрос по TCP.

Формат сообщения одинаков для UDP и TCP: 12-байтовый заголовок (ID, флаги, счётчики) плюс секции question, answer, authority, additional. В TCP перед сообщением добавляется 2-байтовый префикс длины.

Современные резолверы (например, Go's net.Resolver) автоматически переключаются на TCP при получении TC-флага. Также существует DNS over HTTPS (DoH) и DNS over TLS (DoT), но это уже поверх HTTP/3 или TLS, а не классический DNS-транспорт.

На практике

В Go стандартная библиотека net уже реализует корректное поведение: сначала UDP, при TC - TCP. Однако при написании собственного DNS-клиента или сервера нужно учитывать:

  • Для UDP-сервера - обрабатывать каждый запрос в отдельной горутине, так как пакеты могут приходить одновременно.
  • Для TCP-сервера - устанавливать таймауты чтения/записи, чтобы избежать hanging connections.
  • При использовании EDNS0 - проверять, поддерживает ли сервер OPT record, иначе ответ может быть обрезан.
  • Для высоконагруженных резолверов - использовать connection pooling для TCP, так как установка соединения на каждый запрос дорога.

Пример кода

GO
package main
import (
"context"
"fmt"
"net"
"time"
)
func main() {
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: 5 * time.Second,
}
// Принудительно используем UDP, но с fallback на TCP через стандартный механизм
return d.DialContext(ctx, "udp", "8.8.8.8:53")
},
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
ips, err := resolver.LookupIPAddr(ctx, "example.com")
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
for _, ip := range ips {
fmt.Println(ip.IP)
}
}

Этот код демонстрирует кастомный dial для резолвера. Обратите внимание: net.Resolver сам обработает TC-флаг и переключится на TCP, но только если в Dial передать network из параметра (в примере выше мы жёстко задали "udp", что сломает fallback - в реальном коде нужно использовать network из аргумента).

Как отвечать на собеседовании

Начни с прямого ответа: "UDP, порт 53, с TCP как fallback". Затем кратко объясни, почему UDP - основной: низкая latency, отсутствие handshake, простота. Упомяни триггеры для TCP: truncation, zone transfer, DNSSEC. Если спросят про EDNS0 - скажи, что он расширяет размер UDP-пакета, но не отменяет TCP. Для senior-позиции добавь детали: как Go-резолвер обрабатывает TC, какие таймауты ставить, как избежать amplification attacks на UDP-сервере. Не углубляйся в DoH/DoT, если не спросят - это отдельная тема.

Что проверяет интервьюер

  • Понимание транспортных протоколов и их trade-off.
  • Знание ограничений UDP (размер, потеря пакетов) и механизмов компенсации в DNS.
  • Практический опыт: как работает резолвер, что происходит при обрезанном ответе.
  • Умение объяснить, почему нельзя использовать только TCP (latency, overhead) или только UDP (надёжность).
  • Для Go - знание стандартной библиотеки и особенностей net.Resolver.

Типичные ошибки

  • Утверждение, что DNS работает только по UDP - неверно, TCP обязателен для ряда случаев.
  • Игнорирование EDNS0: без него лимит 512 байт, и многие современные ответы (DNSSEC, много A-записей) будут обрезаться.
  • Непонимание, что TC-флаг - это сигнал клиенту повторить запрос по TCP, а не ошибка.
  • В Go - жёсткое задание "udp" в кастомном Dial без учёта параметра network, что ломает fallback.
  • Забывают про таймауты: UDP-запрос без ответа может висеть бесконечно, если не установить deadline.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью