# 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.
Что означают флаги в ответе - `aa` — ответ авторитетен. - `ra` — сервер поддерживает рекурсивные запросы. - `rd` — клиент запросил рекурсию. Сам по себе этот флаг не означает, что сервер действительно выполнял такой поиск.
### Сравнение с публичным 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).