Files
hse-2026/materials/07-naming-lookup/seminar/dns/readme.md
T
2026-10-21 23:03:28 +03:00

136 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DNS: делегирование, кэширование и ошибки
В Consul мы получали адрес из реестра сервисов. Теперь рассмотрим DNS: как найти сервер, который знает нужную запись, и почему разные клиенты могут получать разные адреса одного имени.
**Авторитетный сервер** хранит записи обслуживаемой зоны. **Резолвер** получает ответы для клиента и может сохранять их в кэше. В опытах мы будем обращаться к обоим и сравнивать результаты.
Все команды выполняются из каталога `seminar`; [подготовка стенда](../readme.md#самостоятельный-запуск-опытов).
## 1. Прочитать DNS-ответ
В стенде подготовлена отдельная учебная иерархия:
| Сервер | Роль |
| --- | --- |
| `root-dns` | Обслуживает учебный корень `.` и делегирует зону `test.` |
| `tld-dns` | Обслуживает зону верхнего уровня (TLD) `test.` и делегирует `lab.test.` |
| `auth` | Хранит записи зоны `lab.test.` |
| `resolver` | Получает записи учебной зоны у `auth` и кэширует их |
Делегировать зону — значит указать серверы, отвечающие за неё. Учебная иерархия работает внутри Docker и не меняет публичный DNS.
~~~powershell
docker compose up -d --wait auth resolver root-dns tld-dns
docker compose run --rm -T tools dig "@resolver" orders.lab.test A
docker compose run --rm -T tools dig "@auth" www.lab.test A +norecurse
~~~
В команде `dig` аргумент `@сервер` выбирает получателя запроса, далее идут имя и тип записи. Параметр `+norecurse` отключает запрос рекурсивного поиска: сервер отвечает сведениями, которыми уже располагает.
В первом ответе найдите **A-запись** — IPv4-адрес `orders.lab.test`. Во втором имя `www.lab.test` связано с `orders.lab.test` записью **CNAME**, за которой следует A-запись целевого имени.
При чтении вывода обращайте внимание на:
- `SERVER` — какой сервер ответил;
- `status` — код результата;
- `ANSWER` — записи, отвечающие на запрос;
- число перед `IN` в строке записи — **TTL** — сколько секунд запись ещё можно использовать из кэша.
Адреса `192.0.2.10` и `192.0.2.20` используются как учебные значения записей. Приложений по этим адресам в стенде нет.
## 2. Пройти цепочку делегирований
**Вопрос:** если корневой сервер не хранит адрес `orders.lab.test`, как его ответ помогает продолжить поиск?
~~~powershell
docker compose run --rm -T tools python3 dns/delegation.py
~~~
Скрипт выводит команды и ответы: сначала трассировку `dig +trace`, затем отдельные запросы к корню, TLD-серверу и авторитетному серверу.
В отдельных ответах корня и TLD-сервера секция `ANSWER` пуста. В секции `AUTHORITY` находятся **NS-записи** — имена серверов делегированной зоны. В `ADDITIONAL` даны их адреса, или **glue-записи**, чтобы можно было обратиться к следующему серверу. Последний ответ содержит искомую A-запись.
При `+trace` утилита сама выполняет итеративный поиск: переходит от одного сервера к следующему по делегированиям. Обычный запрос к резолверу может целиком обслужиться из кэша. В нашем опыте `resolver` настроен пересылать запросы учебной зоны сразу к `auth`; трассировка отдельно показывает путь через корень и TLD.
<details>
<summary>Что означают флаги в ответе</summary>
- `aa` — ответ авторитетен.
- `ra` — сервер поддерживает рекурсивные запросы.
- `rd` — клиент запросил рекурсию. Сам по себе этот флаг не означает, что сервер действительно выполнял такой поиск.
</details>
### Сравнение с публичным DNS
~~~powershell
docker compose run --rm -T tools dig wikipedia.org A
docker compose run --rm -T tools dig "@1.1.1.1" wikipedia.org A +trace +time=2 +tries=1
~~~
В конце полной трассировки должна появиться A-запись `wikipedia.org`. Если вывод заканчивается раньше, поиск не завершён, даже если команда вернула код 0. Публичные записи меняются, а сеть может ограничивать прямые обращения к внешним DNS-серверам. Для разбора делегирований в таком случае используйте локальную иерархию.
## 3. Изменить адрес и наблюдать старый TTL
**Предскажите:** если одновременно изменить адрес и уменьшить TTL с 60 до 5 секунд, когда изменится ответ уже заполненного кэша?
Верните исходную запись командой `reset`, очистите кэш командой `flush` и запросите адрес:
~~~powershell
docker compose exec -T auth python3 dns/control.py reset
docker compose exec -T resolver python3 dns/control.py flush
docker compose run --rm -T tools dig "@resolver" orders.lab.test A +noall +answer
~~~
Параметры `+noall +answer` оставляют в выводе только записи ответа. Получим `192.0.2.10` с TTL около 60. Отсчитывая время от этого запроса, примерно через 20 секунд измените запись и сравните ответы:
~~~powershell
docker compose exec -T auth python3 dns/control.py update --address 192.0.2.20 --ttl 5
docker compose run --rm -T tools dig "@auth" orders.lab.test A +noall +answer
docker compose run --rm -T tools dig "@resolver" orders.lab.test A +noall +answer
~~~
Авторитетный сервер уже выдаёт `192.0.2.20` с TTL 5. Резолвер продолжает возвращать `192.0.2.10` с остатком прежних 60 секунд. Уменьшение TTL на авторитетном сервере не меняет срок ранее сохранённой записи. Повторный запрос к кэшу тоже не начинает отсчёт заново.
Дождитесь истечения **TTL старого ответа** и повторите запрос:
~~~powershell
docker compose run --rm -T tools dig "@resolver" orders.lab.test A +noall +answer
~~~
Теперь ожидаем `192.0.2.20` с TTL не больше 5. Если при выполнении предыдущих команд прошла минута и старый адрес уже исчез, повторите опыт с `reset` и `flush`. Для более длинного опыта можно задать `reset --ttl 120`.
Здесь кэш настроен без предварительного обновления и выдачи просроченных записей. После истечения TTL следующий запрос клиента заставляет резолвер получить запись заново.
**Вопросы для самопроверки:** когда уменьшать TTL перед переездом сервиса? Почему старый адрес нужно поддерживать некоторое время? Где ещё, кроме резолвера, могут кэшироваться адреса?
Восстановите исходную запись и очистите кэш:
~~~powershell
docker compose exec -T auth python3 dns/control.py reset
docker compose exec -T resolver python3 dns/control.py flush
~~~
## 4. Различить отсутствие записи и ошибку поиска
Отсутствие адреса в ответе может означать разные вещи. Сравните четыре результата:
~~~powershell
docker compose run --rm -T tools python3 dns/errors.py
~~~
| Запрос / условие | Результат | Что можно заключить |
| --- | --- | --- |
| `missing.lab.test A` | `NXDOMAIN` | Запрошенного имени нет |
| `orders.lab.test AAAA` | `NOERROR`, пустая секция `ANSWER` | Имя существует, но IPv6-адреса для него нет |
| `name.broken.test A` через резолвер | `SERVFAIL` | Резолвер не смог получить пригодный ответ |
| На запрос никто не отвечает | Тайм-аут | Ответ не получен за отведённое время |
**NODATA** — название ситуации, когда имя существует, но записи нужного типа нет; отдельного кода DNS с таким именем нет. В нашем примере запрос AAAA возвращает именно такой результат.
SERVFAIL в опыте возникает из-за пересылки запроса серверу, который не обслуживает нужную зону. Ни SERVFAIL, ни тайм-аут не доказывают отсутствия имени.
**Вопрос:** почему недавно созданное имя может некоторое время оставаться недоступным клиентам, которые запрашивали его до создания? Учтите, что ответы об отсутствии имени тоже могут кэшироваться.
Исходники: [настройка серверов](server.py), [изменение записи и сброс кэша](control.py), [делегирование](delegation.py), [ошибки](errors.py). Справочник: [BIND 9](https://bind9.readthedocs.io/en/v9.18.39/reference.html).