Сети для самых маленьких. Часть 12: DNS
Всё, что мы разбирали до сих пор, работало с адресами: 203.0.113.10. Люди адресами не пользуются, они набирают www.example.com. Кто-то должен перевести одно в другое, и это DNS — Domain Name System, RFC 1034 и 1035, 1987 год. DNS — первое, что делает браузер при открытии страницы, первое, что ломается при неправильной настройке сети, и самая большая распределённая база данных в мире: несколько сотен миллионов имён, ни одного центрального сервера, и ответ за десятки миллисекунд из любой точки планеты.
Как в прошлых частях, нас интересует не «DNS превращает имена в адреса», а как именно: кто хранит, кто спрашивает, что едет в пакете.
Дерево имён
Имя www.example.com. — путь в дереве от корня к листу, записанный справа налево:
. (корень)
┌───────────────┼───────────────┐
com org ru
┌────┴────┐ │
example google wikipedia
│
www
- Корень — пустая метка, в полном имени обозначается точкой в конце:
www.example.com.. Такое имя называется FQDN (fully qualified); без точки резолвер может попробовать дописать к имени домен поиска. - Первый уровень — TLD:
com,org,ru,byи несколько сотен других. - Каждая метка до 63 байт, всё имя до 253; регистр не имеет значения:
WWW.Example.COMиwww.example.com— одно имя.
Дерево никто не хранит целиком. Оно разрезано на зоны — куски, за каждый из которых отвечает своя организация:
- корневая зона — список TLD, её держит IANA, а обслуживают 13 «корневых серверов»
a.root-servers.net…m.root-servers.net(на самом деле более полутора тысяч машин по всему миру с теми же 13 адресами, об этом ниже); - зона
com.— список всех доменов второго уровня в.com, её держит регистратура Verisign; - зона
example.com.— её держит владелец домена на своих серверах или у хостера.
Граница между зонами — делегирование: в зоне com. нет записей про www.example.com, там есть только «за example.com отвечают серверы такие-то». Поэтому владелец домена может менять что угодно внутри своей зоны, не трогая регистратуру.
Три вида участников
В разрешении имени участвуют три разных программы, и их путают чаще всего.
Stub-резолвер — часть операционной системы. Сам ничего не ищет: берёт имя у приложения, отправляет запрос рекурсивному серверу из своих настроек и возвращает ответ. Настройки — /etc/resolv.conf в Linux (или systemd-resolved, который слушает на 127.0.0.53 и сам ходит дальше), адреса DNS из DHCP прошлой части или заданные руками в свойствах адаптера Windows. Перед запросом он смотрит в /etc/hosts: имя, найденное там, в сеть не уходит.
Рекурсивный сервер (резолвер) — тот, кто делает всю работу: ходит по дереву от корня вниз, пока не найдёт ответ, и кеширует всё, что узнал. Это DNS-сервер провайдера, 8.8.8.8 Google, 1.1.1.1 Cloudflare, или ваш домашний роутер, который сам ничего не ищет, а пересылает запросы провайдеру (такой режим называется forwarding).
Авторитативный сервер — хранит зону и отвечает только за неё. Ничего не ищет и не кеширует: что в зоне есть, то и отдаёт, с флагом «ответ авторитетный». У каждой зоны минимум два таких сервера — primary, на котором зону редактируют, и secondary, которые забирают с него копию (зонная передача, AXFR целиком или IXFR изменениями, по TCP) и отвечают наравне.
Путь запроса
Браузер хочет www.example.com. Кеш рекурсивного сервера пуст:
stub рекурсивный сервер R корневой com. example.com.
│ │ │ │ │
├── A? www.example.com ───►│ │ │ │
│ (RD = 1, «сделай всё») │ │ │ │
│ ├── A? www.example.com ─────►│ │ │
│ │◄─ «не знаю, но com. — вот ─┤ │ │
│ │ NS a.gtld-servers.net │ │ │
│ │ + их адреса (glue)» │ │ │
│ ├── A? www.example.com ──────────────────►│ │
│ │◄── «example.com. — вот NS ──────────────┤ │
│ │ ns1.example.com + glue» │ │
│ ├── A? www.example.com ──────────────────────────────────►│
│ │◄── A 203.0.113.10, TTL 3600, AA = 1 ────────────────────┤
│◄── A 203.0.113.10 ───────┤ │
│ │ в кеш: NS com. (2 дня), NS example.com. (2 дня), │
│ │ www.example.com A (1 час) │
- Stub шлёт запрос с флагом RD (recursion desired): «найди сам». Это рекурсивный запрос.
- R спрашивает корневой сервер. Корневой не знает
www.example.com, но знает, кто отвечает заcom., и присылает отсылку (referral): NS-записи серверовcom.и их адреса. Это итеративный запрос: «скажи, что знаешь, дальше я сам». - R спрашивает сервер
com.. Тот отсылает к серверамexample.com.. - R спрашивает
ns1.example.comи получает авторитетный ответ с флагом AA. - R отдаёт ответ stub-у и кладёт в кеш каждый шаг со своим TTL.
Следующий запрос про mail.example.com пойдёт сразу на шаг 4: серверы example.com уже в кеше. Запрос про www.google.com — на шаг 3: серверы com. в кеше. К корневым серверам хороший резолвер обращается раз в двое суток, и именно поэтому 13 адресов выдерживают нагрузку всей планеты. Остальное делает anycast: один адрес 198.41.0.4 анонсируется из сотен точек, и маршрутизация приводит вас к ближайшей.
Откуда R знает адреса корневых серверов, если спросить их не у кого? Из файла, вшитого в сервер (root hints), и это единственное, что нужно настроить руками.
Делегирование и glue
На шаге 2 корневой сервер прислал не только имена серверов com., но и их адреса. Иначе R пришлось бы спрашивать «а какой адрес у a.gtld-servers.net?» — у кого? У серверов net., адреса которых тоже нужно узнать. Записи с адресами серверов, которые родительская зона хранит у себя вместе с NS-записями, называются glue, и без них делегирование внутрь своей же зоны (example.com обслуживается ns1.example.com) зациклилось бы. Когда у домена меняют NS-серверы, нужно поменять и glue у регистратора — об этом забывают, и домен продолжает отвечать со старых серверов, пока те живы.
Сообщение
DNS едет в UDP, порт 53, одно сообщение в одной датаграмме. Запрос и ответ одного формата:
┌───────────────────────────────┐
│ Заголовок, 12 байт │ ID, флаги, четыре счётчика
├───────────────────────────────┤
│ Question │ что спрашивали: имя, тип, класс
├───────────────────────────────┤
│ Answer │ записи-ответы
├───────────────────────────────┤
│ Authority │ NS-записи зоны (в отсылках) или SOA (в отказах)
├───────────────────────────────┤
│ Additional │ glue, OPT и прочее полезное
└───────────────────────────────┘
Заголовок:
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
┌───────────────────────────────┐
│ ID │ 16 бит, выбирает клиент; ответ несёт тот же
├─┬───────┬─┬─┬─┬─┬─────┬───────┤
│Q│Opcode │A│T│R│R│ Z │ RCODE │
│R│ │A│C│D│A│ │ │
├─┴───────┴─┴─┴─┴─┴─────┴───────┤
│ QDCOUNT │ сколько вопросов (всегда 1)
│ ANCOUNT │ записей в Answer
│ NSCOUNT │ в Authority
│ ARCOUNT │ в Additional
└───────────────────────────────┘
- QR — 0 запрос, 1 ответ. AA — ответ авторитетный. TC — ответ обрезан, не влез в датаграмму. RD — клиент просит рекурсию. RA — сервер её умеет.
- RCODE:
0NOERROR,2SERVFAIL (сервер не смог: авторитативные не отвечают, ошибка DNSSEC),3NXDOMAIN (такого имени нет),5REFUSED (сервер отказался: вы не его клиент). Отдельный случай — NODATA: имя есть, но записей такого типа нет; это NOERROR с пустым Answer.
Имена кодируются не строкой, а цепочкой меток с длиной впереди: www.example.com — это 3 w w w 7 e x a m p l e 3 c o m 0. Ноль — корень. Чтобы не повторять одно и то же имя в каждой записи ответа, есть сжатие: указатель на место в сообщении, где это имя уже было.
Запись (RR, resource record) в секциях ответа — имя, тип, класс (всегда IN), TTL, длина, данные. Так выглядит ответ в tcpdump:
$ sudo tcpdump -i eth0 -n udp port 53
IP 192.168.1.10.40321 > 192.168.1.1.53: 12345+ A? www.example.com. (33)
IP 192.168.1.1.53 > 192.168.1.10.40321: 12345 1/0/0 A 203.0.113.10 (49)
12345 — ID, + — RD, A? — вопрос, 1/0/0 — по одной записи в Answer и ни одной в Authority и Additional.
Типы записей
| Тип | Что в данных | Для чего |
|---|---|---|
| A | IPv4-адрес | имя → адрес; у имени может быть несколько, резолвер вернёт все, клиент возьмёт любую |
| AAAA | IPv6-адрес | то же для IPv6 (часть 13); браузер спрашивает A и AAAA одновременно |
| CNAME | другое имя | псевдоним: «www.example.com — это на самом деле example.hosting.net»; сервер, встретив CNAME, продолжает искать запрошенный тип у цели и возвращает всю цепочку. У имени с CNAME не может быть других записей, поэтому CNAME нельзя поставить на вершину зоны, где обязаны быть SOA и NS |
| NS | имя сервера | кто авторитативен за зону; стоит и в родительской зоне (делегирование), и в самой |
| SOA | primary, почта админа, serial, refresh, retry, expire, minimum | паспорт зоны: serial растёт при каждом изменении, по нему secondary понимают, что пора забрать копию; refresh/retry/expire — как часто проверять и сколько жить без primary; minimum — TTL отрицательных ответов |
| MX | приоритет, имя сервера | куда слать почту для домена: почтовый сервер отправителя (он говорит по SMTP — протоколу, которым серверы передают друг другу письма) спрашивает MX, берёт с наименьшим приоритетом, для него спрашивает A |
| PTR | имя | обратное разрешение: адрес → имя, через специальное дерево in-addr.arpa: для 203.0.113.10 спрашивают 10.113.0.203.in-addr.arpa (октеты задом наперёд, потому что дерево идёт от общего к частному). Почтовые серверы по PTR проверяют отправителей |
| TXT | произвольный текст | подтверждение владения доменом, политики почты: SPF (кто имеет право слать от имени домена), DKIM (ключи подписи писем) |
| SRV | приоритет, вес, порт, имя | где искать сервис: _ldap._tcp.example.com → сервер и порт, на котором отвечает каталог LDAP (протокол доступа к справочнику пользователей и компьютеров); так машины в домене Windows находят контроллеры |
| CAA | удостоверяющий центр | кому разрешено выпускать сертификаты для домена |
TTL и кеширование
У каждой записи есть TTL в секундах — сколько её можно держать в кеше. Выставляет владелец зоны, а соблюдают все рекурсивные серверы и, частично, stub-ы и браузеры. Авторитативный ответ приходит с полным TTL, из кеша — с остатком, и в dig видно, как второй запрос показывает меньшее число.
Отсюда «распространение DNS», которое длится «до 48 часов»: ничего никуда не распространяется, просто кеши по миру досматривают старую запись до конца её TTL. Правильный порядок переезда сервера: за сутки до переезда снизить TTL записи до 5 минут, дождаться, пока старый TTL везде истечёт, поменять адрес, вернуть TTL.
Кешируются и отрицательные ответы: NXDOMAIN помнится столько, сколько сказано в поле minimum SOA зоны (обычно час). Поэтому «я только что создал запись, а её нет» — сначала спросили, потом создали, и резолвер час будет отвечать «нет».
Кешируются и ошибки на уровне stub: systemd-resolved и Windows держат свой кеш, resolvectl flush-caches и ipconfig /flushdns его чистят.
UDP, TCP, EDNS
Исторический лимит датаграммы DNS — 512 байт. Ответ, который не влезает, сервер обрезает, ставит флаг TC, и клиент повторяет запрос по TCP на тот же 53 порт: рукопожатие, запрос с двухбайтовой длиной впереди, ответ любого размера. TCP нужен и для зонной передачи.
Чтобы не ходить в TCP за каждым ответом с несколькими адресами и подписями, придумали EDNS(0): клиент добавляет в Additional псевдозапись OPT с размером датаграммы, который он готов принять. Сейчас принято объявлять 1232 байта — столько гарантированно пролезает без фрагментации через любой разумный MTU, а фрагментированные датаграммы, как мы знаем из шестой части, теряются и фильтруются. В OPT же живут расширения вроде подсети клиента (чтобы CDN дал ближайший адрес) и флаг DO для DNSSEC.
Что пропускать в файрволе: UDP и TCP на 53 в обе стороны. Правило «DNS — это UDP», закрывающее TCP, ломает большие ответы, DNSSEC и зонные передачи.
Делегирование на практике: dig +trace
Весь путь запроса можно пройти самому:
$ dig +trace www.example.com
. 518400 IN NS a.root-servers.net.
. 518400 IN NS b.root-servers.net.
...
;; Received 525 bytes from 192.168.1.1#53(192.168.1.1) in 2 ms
com. 172800 IN NS a.gtld-servers.net.
com. 172800 IN NS b.gtld-servers.net.
...
;; Received 1170 bytes from 198.41.0.4#53(a.root-servers.net) in 15 ms
example.com. 172800 IN NS ns1.example.com.
example.com. 172800 IN NS ns2.example.com.
;; Received 220 bytes from 192.5.6.30#53(a.gtld-servers.net) in 20 ms
www.example.com. 3600 IN A 203.0.113.10
;; Received 60 bytes from 192.0.2.53#53(ns1.example.com) in 25 ms
Первый блок — корневые серверы из кеша вашего резолвера; второй — ответ корневого: отсылка к com.; третий — ответ com.: отсылка к example.com.; четвёртый — авторитетный ответ. Если на каком-то шаге отсылка ведёт на серверы, которые не отвечают или отвечают другим — вот где сломано делегирование.
Разное, что встретится
Домены поиска. Stub дописывает к коротким именам домены из search в resolv.conf или опции 119 DHCP: ping server → server.example.com. Правило ndots решает, пробовать ли сначала имя как есть; в Kubernetes с его ndots:5 каждый внешний запрос сначала проходит по четырём внутренним доменам и только потом идёт как есть, отсюда лишние запросы и странные задержки.
Split-horizon. Один и тот же www.example.com изнутри сети резолвится в 192.168.1.50, снаружи — в 203.0.113.10: внутренний DNS-сервер держит свою версию зоны. Нормальная практика, пока ноутбук не принёс домой кеш с внутренним адресом.
Round-robin. Несколько A-записей у одного имени — сервер перемешивает порядок в каждом ответе, и нагрузка распределяется. Не балансировка: мёртвый сервер из списка не исчезает.
Безопасность. Подделать ответ, зная ID (16 бит) и порт запроса, было легко, пока резолверы не начали брать случайный порт отправителя на каждый запрос: это отравление кеша, и после атаки Камински 2008 года так делают все. Настоящая защита — DNSSEC: владелец зоны подписывает записи закрытым ключом (записи RRSIG), публикует открытый (DNSKEY), а хеш открытого ключа кладёт в родительскую зону (DS) — так от корня до зоны строится цепочка доверия, и резолвер, проверяющий подписи, не примет подделку. DNSSEC защищает подлинность, но не приватность: запросы по-прежнему в открытом виде. Для приватности есть DoT — тот же DNS внутри TLS-соединения на порту 853, и DoH — DNS-сообщения как запросы HTTPS (протокола HTTP, по которому браузер получает страницы, внутри того же TLS) на порту 443, неотличимые от веб-трафика; и то и другое закрывает запросы от провайдера, но не от самого резолвера.
Смотрим руками
Главный инструмент — dig:
$ dig www.example.com
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12345
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;www.example.com. IN A
;; ANSWER SECTION:
www.example.com. 3600 IN A 203.0.113.10
;; Query time: 12 msec
;; SERVER: 192.168.1.1#53(192.168.1.1) (UDP)
;; MSG SIZE rcvd: 60
Читаем: status: NOERROR; флаги qr rd ra — ответ, рекурсию просили, сервер умеет, aa нет — ответ из кеша или через рекурсию, не от авторитативного; EDNS с 1232; TTL 3600 — полный, значит, резолвер только что сходил за записью. Повторите через минуту — TTL станет 3540.
Полезные варианты:
$ dig @ns1.example.com www.example.com # спросить конкретный сервер (ответ будет с aa)
$ dig example.com MX # другой тип
$ dig -x 203.0.113.10 # PTR
$ dig +short www.example.com # только данные
$ dig +norecurse @192.168.1.1 www.example.com # есть ли в кеше: без RD сервер искать не пойдёт
$ dig example.com SOA # serial и TTL отрицательных ответов
Что настроено у stub-а: resolvectl status (systemd), cat /etc/resolv.conf, на Windows ipconfig /all и Resolve-DnsName www.example.com в PowerShell, которая показывает и цепочку CNAME.
И tcpdump на udp port 53, когда непонятно, уходит ли запрос вообще и откуда приходит ответ: половина «DNS не работает» — это запросы, уходящие не на тот сервер.
Что запомнить
- Дерево имён разрезано на зоны; каждую обслуживают свои авторитативные серверы, родительская зона хранит делегирование (NS) и glue (адреса).
- Три участника: stub в ОС просит рекурсию, рекурсивный сервер ходит по дереву от корня и кеширует, авторитативный отвечает только за свою зону.
- Сообщение: 12-байтовый заголовок с ID и флагами (QR, AA, TC, RD, RA, RCODE), секции Question, Answer, Authority, Additional. NXDOMAIN — имени нет, NODATA — нет записей такого типа.
- Записи: A/AAAA адреса, CNAME псевдоним (не на вершине зоны), NS и SOA — устройство зоны, MX почта, PTR обратное, TXT, SRV, CAA.
- TTL правит кешем: «распространение» — это истечение старых записей; отрицательные ответы кешируются по SOA minimum.
- UDP 53 с EDNS 1232, при TC — TCP 53; оба должны быть открыты. DNSSEC — подлинность, DoT/DoH — приватность.
На этом служебные протоколы IPv4 закончены. Дальше — IPv6: 128-битные адреса, их запись и типы, почему на интерфейсе всегда несколько адресов, что убрали из заголовка по сравнению с IPv4 и зачем, и почему там нет широковещания, фрагментации на роутерах и NAT.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.