Современный DNS — DoH, DoT, DNSSEC, EDNS
Классический DNS, описанный в RFC 1034 в 1987 году, имеет два больших структурных недостатка: нет шифрования (любой на пути видит, какие сайты ты резолвишь) и нет аутентификации (ответ можно подделать). За последние 10-15 лет появилось несколько расширений, решающих эти проблемы.
В этом уроке разберём:
- DoH (DNS over HTTPS) — DNS-запросы внутри HTTPS-трафика.
- DoT (DNS over TLS) — DNS поверх TLS на отдельном порту.
- DNSSEC — криптографическая подпись DNS-записей.
- EDNS — расширения протокола DNS (большие пакеты, опции).
- Что использовать в 2026 году.
Проблема классического DNS
Классический DNS использует UDP/53 (или TCP/53 для больших ответов). Это plaintext-протокол:
$ sudo tcpdump -i any -n -X 'udp port 53' -c 1
14:30:00.123 IP 192.168.1.10.54321 > 8.8.8.8.53: 12345+ A? www.example.com. (33)
0x0000: 4500 003d ... E..=.....@.
...
0x0030: 7777 7706 6578 616d 706c 6503 www.example.
0x0040: 636f 6d00 0001 0001 com.....
Любой между тобой и resolver’ом (твой роутер, провайдер, владелец Wi-Fi точки в кафе, корпоративный proxy) видит, какие сайты ты резолвишь. Это privacy-проблема:
- Провайдер знает твою историю. Не только URL, но и какие именно сайты ты открываешь.
- Government surveillance. В некоторых странах провайдеры обязаны логировать DNS.
- Censorship. Многие фильтры работают на уровне DNS (блокируют резолв запрещённых сайтов).
- Tampering. ISP может подменить DNS-ответ — например, ввести ad-injection.
Решение — шифрование DNS-канала. Появились два стандарта: DoT и DoH.
DNS over TLS (DoT)
DoT (RFC 7858, 2016) — DNS поверх TLS. Принцип простой: вместо отправки запроса по UDP/53 plaintext, клиент устанавливает TLS-соединение с resolver’ом на порт 853 и шлёт DNS-сообщения внутри TLS-туннеля.
Преимущества DoT:
- Privacy. Содержимое запросов скрыто от посредников.
- Integrity. TLS защищает от подмены ответов.
- Authentication. Сертификат resolver’а проверяется — ты говоришь с настоящим 1.1.1.1, не с подменой.
Недостаток: легко обнаружимый. Трафик на порт 853 — это явно DoT. Файрвол может его блокировать. В некоторых странах это уже сделано.
Реализации:
- Cloudflare 1.1.1.1, 1.0.0.1 — DoT на :853.
- Google 8.8.8.8 — DoT на :853.
- Quad9 9.9.9.9 — DoT на :853.
- Системная поддержка: systemd-resolved (Linux), Android Private DNS (с 9.0+).
# Тестим DoT с openssl:
echo "" | openssl s_client -connect 1.1.1.1:853 -servername cloudflare-dns.com 2>/dev/null | head -10
# Должен установиться TLS-канал
# Тестим DoT с kdig (если установлен):
kdig +tls @1.1.1.1 example.com
# Ответ как обычно, но через TLS
DNS over HTTPS (DoH)
DoH (RFC 8484, 2018) — DNS-запросы внутри HTTPS-трафика. Это «глубже» чем DoT: запросы выглядят как обычные HTTPS-запросы к веб-сайту, никак не отличить.
DoH-запрос — это POST или GET к URL вида https://cloudflare-dns.com/dns-query:
POST /dns-query HTTP/2
Host: cloudflare-dns.com
Content-Type: application/dns-message
Content-Length: 33
<binary DNS message>
Ответ — Content-Type: application/dns-message с binary DNS-ответом в теле.
Преимущества DoH над DoT:
- Невидимость. Трафик неотличим от обычного HTTPS. Сложнее блокировать без блокировки всего HTTPS.
- Использует существующую HTTPS-инфраструктуру (HTTP/2, HTTP/3, CDN, CORS).
- Поддерживается браузерами напрямую — Firefox, Chrome могут резолвить через DoH, минуя ОС-resolver.
Недостатки:
- Спор о философии. Если браузер сам делает DoH, минуя ОС, корпоративные DNS-фильтры перестают работать. Возникает напряжение между security и privacy.
- Performance overhead. HTTP/2/3 + JSON parsing медленнее чистого DNS-протокола.
Популярные DoH-resolvers:
https://cloudflare-dns.com/dns-query(Cloudflare)https://dns.google/dns-query(Google)https://doh.opendns.com/dns-query(Cisco OpenDNS)
# Тест DoH через curl:
echo -n "AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE" | base64 -d > /tmp/query.bin
curl -s -H 'accept: application/dns-message' \
--data-binary @/tmp/query.bin \
-H 'content-type: application/dns-message' \
-X POST 'https://cloudflare-dns.com/dns-query' | xxd | head
# Или через `kdig` если есть:
kdig +https=/dns-query @cloudflare-dns.com example.com
# В Firefox DoH включается через настройки Network -> 'Enable secure DNS over HTTPS'
# По умолчанию использует Cloudflare 1.1.1.1
DoH в браузерах — споры
В 2019 Mozilla и Cloudflare запустили дефолтный DoH в Firefox для US-пользователей. Это вызвало споры:
За (privacy):
- ISP больше не видит DNS-запросы.
- Не нужно доверять провайдеру.
- Защита от DNS-фильтров и подмены ответов.
Против (corporate/family security):
- Школы и компании не могут блокировать опасные сайты через DNS.
- Parental control на DNS-уровне не работает.
- Антивирусные DNS-фильтры (Quad9, OpenDNS) обходятся.
- Бэкап провайдер (если 1.1.1.1 падает) не известен.
В Chrome DoH включается «condition-based»: если ОС использует resolver, который тоже поддерживает DoH, Chrome переключается на DoH к нему. Иначе использует системный.
В Firefox можно полностью отключить или задать свой DoH-resolver.
DNSSEC — криптографическая подпись
DoH и DoT защищают канал между клиентом и resolver’ом. Но resolver всё равно может лгать (или быть скомпрометированным). Что если мы хотим криптографически верифицировать, что ответ действительно от authoritative-сервера?
DNSSEC (DNS Security Extensions, RFC 4033-4035) — это набор расширений, добавляющий цифровые подписи в DNS. Идея:
- Каждая зона имеет ключевую пару.
- Все записи зоны подписываются (RRSIG-записи).
- Публичный ключ публикуется как DNSKEY-запись.
- Цепочка доверия от root: DS-запись родительской зоны подписывает ключи дочерней.
Когда resolver получает DNSSEC-подписанный ответ, он:
- Получает запись + RRSIG (подпись).
- Получает DNSKEY этой зоны.
- Верифицирует RRSIG с DNSKEY.
- Получает DS-запись для этой зоны из родительской.
- Верифицирует DNSKEY соответствует DS.
- Поднимается выше по chain, до root.
- Корневой ключ известен apriori (trust anchor).
Если что-то не сошлось — ответ невалиден, resolver возвращает SERVFAIL.
# Проверь, подписана ли зона DNSSEC:
dig +dnssec example.com
# Если есть RRSIG-записи -- да, подписана
# Также флаг 'ad' (Authenticated Data) -- если resolver валидировал
# Проверь chain:
dig +sigchase example.com
# Покажет всю цепочку проверок (если dig скомпилирован с поддержкой)
TLS handshake и certificates — та же модель доверия что в DNSSEC
DNSSEC не очень популярен — adoption ~5-10% в большинстве TLD. Причины:
- Сложность. Управление ключами, ротация, ошибки приводят к долгому downtime.
- Не решает privacy. Запросы всё равно plaintext (если без DoH/DoT).
- Increased size. DNS-ответы с подписями больше — UDP-фрагментация, амплификация для DDoS.
Но для критических зон (банки, госструктуры, .gov) DNSSEC обязателен.
EDNS — расширения протокола
Классический DNS-пакет имеет ограничения:
- UDP размер: 512 байт (по RFC 1035).
- Заголовок: фиксированный, нет места для опций.
EDNS (EDNS0, RFC 6891) — расширение, позволяющее:
- UDP-ответы больше 512 байт. Клиент в запросе говорит «я могу принять 4096 байт», и authoritative-сервер может ответить большим UDP-сегментом.
- Опции в запросах. Дополнительные «псевдо-записи» типа OPT добавляются в additional section.
Это нужно для:
- DNSSEC. Подписи делают ответы больше 512 байт — без EDNS не работает.
- EDNS Client Subnet (ECS). Resolver может в запросе сообщить authoritative-серверу IP-блок клиента — для CDN-оптимизации.
- Cookies. DNS Cookies (RFC 7873) — защита от amplification и spoofing.
- DoH/DoT использование — расширения для шифрованных каналов.
# Большой UDP-ответ с EDNS:
dig +bufsize=4096 example.com TXT
# Можешь получить ответ до 4 KB одной UDP-датаграммой
# Без EDNS -- переключение на TCP при ответе > 512 байт:
dig +bufsize=512 +ignore example.com TXT
Современные DNS-стеки используют EDNS по дефолту. Если ты видишь в Wireshark/tcpdump в additional section запись типа OPT — это EDNS.
Что использовать в 2026
SSH и безопасный канал — знакомый пример зашифрованного транспортаДля конечного пользователя:
-
Базовый уровень privacy: включи DoH в браузере (Firefox или Chrome). Это блокирует ISP-перехват DNS на трафике браузера.
-
Системный уровень: настрой ОС использовать DoT-resolver. На Android 9+ есть «Private DNS» в настройках. На macOS — через config profiles или DNSPrivacy. На Linux — systemd-resolved с DNS=1.1.1.1#cloudflare-dns.com.
-
Полный уровень: свой DoH-resolver внутри сети (Pi-hole с DoH, AdGuard Home), направляющий вышестоящие запросы через DoT/DoH.
Для разработчика, делающего DNS-запросы:
- Используй DoH/DoT когда возможно. Стандартная DNS-инфраструктура plaintext.
- DNSSEC только для критичных кейсов. Сложно для большинства сценариев.
- Помни о EDNS при работе с большими ответами.
Для administrator’а:
- Если поддерживаешь публичный resolver: поддерживай DoH и DoT. Cloudflare, Google уже сделали — стандарт индустрии.
- Если authoritative-сервер: EDNS обязателен. DNSSEC опционален, но желателен.
Попробуй сам
# 1. Сравни классический DNS и DoH:
# Classic:
sudo tcpdump -i any -n 'udp port 53' -c 5 &
dig example.com
# Видишь plaintext-запрос
# DoH:
sudo tcpdump -i any -n 'tcp port 443 and host cloudflare-dns.com' -c 10 &
curl -s -H 'accept: application/dns-message' \
'https://cloudflare-dns.com/dns-query?dns=AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE' \
| xxd | head
# Видишь зашифрованный HTTPS-трафик, но не содержимое
# 2. Проверь, подписана ли зона DNSSEC:
dig +short +dnssec dnssec-failed.org RRSIG
# (Это специальный тестовый домен)
# 3. Проверь, валидирует ли твой resolver DNSSEC:
dig dnssec-failed.org A @8.8.8.8
# Должно быть SERVFAIL -- Google валидирует DNSSEC и видит сломанную подпись
dig dnssec-failed.org A @9.9.9.9
# Quad9 тоже валидирует -- SERVFAIL
# 4. Browser DoH (Firefox):
# about:networking#dns -- посмотри статус DoH
# В Network preferences можно включить/настроить
# 5. macOS:
# DNS-конфигурацию по интерфейсу:
scutil --dns | head -30
# 6. Запусти свой DoH-клиент в Python:
python3 << 'EOF'
import requests, struct
# DNS-запрос для example.com
# (Стандартный binary формат, упрощённо)
query = bytes.fromhex('00010100000100000000000007' \
'6578616d706c6503636f6d000001' \
'0001')
r = requests.post('https://cloudflare-dns.com/dns-query',
data=query,
headers={'Content-Type': 'application/dns-message',
'Accept': 'application/dns-message'})
print(f"Status: {r.status_code}, Body: {r.content[:50].hex()}")
EOF
# 7. ipv6 DoH:
# https://[2606:4700:4700::1111]/dns-query -- Cloudflare IPv6