В журналах ничего не выглядит неправильным: интерфейс поднят, резолвер сообщает DNS-сервер VPN, и файл на диске его перечисляет. И всё же внутреннее имя хоста по-прежнему разрешается через локальный маршрутизатор. Эти три утверждения не противоречат друг другу, потому что файл, состояние каждого соединения и путь запроса — разные объекты, случайно имеющие одно имя. Далее описан путь, который запрос действительно проходит на хосте с systemd-resolved.

Три объекта с именем resolv.conf

На хосте с resolved /etc/resolv.conf обычно является символической ссылкой на /run/systemd/resolve/stub-resolv.conf. В этой цели находятся адрес локального stub-слушателя, 127.0.0.53, и домены поиска, вычисленные resolved. Это представление состояния, а не само состояние.

Состояние живёт в конфигурации resolved для каждого соединения, и поэтому resolvectl status может расходиться с файлом и всё равно быть прав. Каждое соединение несёт свои серверы и свои домены маршрутизации, а stub обращается к этим данным, когда приходит запрос. Руководство по systemd-resolved описывает создаваемые файлы и модель для каждого соединения; разделение представления и состояния — та часть, которую стоит запомнить. Изменение цели символической ссылки меняет то, что читают традиционные клиенты, а не то, что знает резолвер.

Третий объект, /run/systemd/resolve/resolv.conf, сплющивает каждое соединение в один список серверов. Программа, направленная на этот файл, отправляет запросы прямо перечисленным серверам и обходит stub, поэтому политика каждого соединения на неё больше не действует. Поэтому две программы на одной машине могут отвечать на одно имя по-разному, и ни одна из них не сломана.

Проследите один запрос через NSS

glibc читает /etc/nsswitch.conf, чтобы решить, кто отвечает на имя. Строка hosts, содержащая resolve, направляет вопрос в nss-resolve, который спрашивает resolved через D-Bus и получает ответ для соединения. Строка только с files dns отправляет запрос на адрес, который содержит /etc/resolv.conf.

Эта одна строка объясняет большинство расхождений между браузером и dig. dig читает resolv.conf напрямую и никогда не касается NSS, поэтому проверяет stub или вышестоящий сервер, а не путь, который использует приложение. getent hosts internal.example идёт через NSS, и сравнение его ответа с ответом dig — первое полезное измерение.

Приложения, которые несут собственный резолвер или говорят DNS через HTTPS с фиксированной конечной точкой, обходят оба пути. Ничто в resolv.conf или в состоянии resolved их не ограничивает.

Проверьте, кому принадлежит файл и куда он указывает

Прочитайте цепочку ссылок, прежде чем что-либо менять, потому что владение определяет, какой файл главный. Каждая команда ниже только читает состояние.

stat -c '%N' /etc/resolv.conf
resolvectl status --no-pager
grep '^hosts' /etc/nsswitch.conf
ls -l /run/systemd/resolve/

Первая строка печатает ссылку и её цель вместе, а обычный файл, принадлежащий VPN-скрипту, означает, что резолвер никогда не считает эту запись входными данными. Вывод состояния перечисляет серверы для каждого соединения и домены маршрутизации, которые решают, куда идёт каждый запрос. Строка nsswitch говорит, обращаются ли к stub вообще, поэтому адрес stub в файле без resolve означает, что запросы достигают локального слушателя, пока ничто не управляет разделением. Последний список тоже важен, потому что хост без stub-resolv.conf не запускает описанную здесь схему.

Если цепочка заканчивается файлом, которым управляет другой пакет, VPN-клиент и этот пакет — два владельца, которых нужно согласовать. Замена ссылки выбрасывает работу одного владельца и оставляет работу другого на месте.

Исправляйте у владельца ссылки

VPN-соединение должно регистрировать свои серверы и свои домены на своём соединении, а затем отзывать их при отключении. Интеграция resolved в NetworkManager и собственный помощник клиента работают именно так; скрипт, который только переписывает resolv.conf, — нет.

На уже существующем соединении механизм показывают настройки времени выполнения. resolvectl dns tun0 10.40.0.53 прикрепляет сервер, а resolvectl domain tun0 '~corp.example' прикрепляет домен маршрутизации. Тильда — несущий символ: она ограничивает запросы этого суффикса этим соединением и оставляет всё остальное в покое, тогда как то же значение без неё становится доменом поиска, и разделение исчезает. Эти адреса — лабораторные примеры, а не постоянная конфигурация. Подтвердите с resolvectl query host.corp.example и прочитайте, какое соединение ответило.

Зарегистрированному серверу всё ещё нужен маршрут. Когда запрос доходит до нужного соединения и истекает по времени, неисправность в доступности через туннель, а не во владении резолвером.

Как выглядит рабочее состояние

Здоровый результат согласован во всех трёх представлениях: серверы каждого соединения совпадают с тем, что выдал VPN, домены маршрутизации охватывают только внутренние суффиксы, а важное приложение разрешает внутреннее имя, тогда как публичное имя по-прежнему идёт локальным путём. Один успешный запрос доказывает меньше, чем это согласие.

Затем отключитесь и повторите оба запроса. Клиент, который оставляет свои домены зарегистрированными после разрыва, создаёт машину, которая через часы не справляется с внутренними запросами, без открытого туннеля и без очевидной причины.

Похожие статьи