Ваш ноутбук подключён к VPN, dig service.internal.example отвечает адресом 10.x, а curl всё равно завершается с Could not resolve host. Туннель здесь ни при чём: он без возражений передал запрос dig. Разница в том, кто задаёт вопрос. dig отправляет один запрос одному серверу, который вы указали, а curl проходит через цепочку поиска операционной системы, и у этой цепочки свои правила о том, какой сервер будет опрошен.

Два поиска для одного имени

dig — это собственный stub resolver. Он читает /etc/resolv.conf, выбирает сервер имён, отправляет один запрос A или AAAA и печатает ответ. Он полностью пропускает nsswitch и ничего не знает о состоянии на уровне отдельного подключения, которое systemd-resolved или NetworkManager хранят вне этого файла, поэтому ответ dig может быть правильным и при этом ничего не говорить о том, что увидит ваше приложение.

Приложения вызывают getaddrinfo, а getaddrinfo проходит более длинную цепочку: порядок модулей nsswitch, затем модуль, который обрабатывает имя, и наконец resolver, построенный из текущей конфигурации подключения. На машине с systemd запрос уходит на 127.0.0.53, а systemd-resolved выбирает вышестоящий сервер для каждого подключения по домену маршрутизации, привязанному к интерфейсу tun.

Две команды разделяют эти уровни без догадок:

getent ahosts service.internal.example
resolvectl query service.internal.example

getent использует тот же путь, что и curl, поэтому сбой getent рядом с работающим dig локализует проблему в политике поиска, а не в маршрутизации пакетов. resolvectl query также печатает интерфейс и вышестоящий сервер, который дал ответ, и это показывает, победил ли линк туннеля или физический линк.

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

/etc/resolv.conf генерируется на большинстве десктопов, и у VPN-клиента есть три способа взаимодействия с ним: добавить сервер имён в начало, перезаписать файл или передать свои серверы NetworkManager и позволить демону перезаписать файл. Только третий вариант сохраняет DNS на уровне линка, потому что серверы остаются привязанными к интерфейсу, а не сплющиваются в один глобальный список.

В системах с NetworkManager результат зависит от backend в /etc/NetworkManager/NetworkManager.conf:

[main]
dns=systemd-resolved

При dns=default серверы клиента сливаются в resolv.conf в том порядке, который победит, и приватный запрос всё ещё может уйти публичному resolver. С backend, понимающим линки, systemd-resolved или dnsmasq, серверы туннеля остаются привязанными к туннелю, и туда уходят только имена, соответствующие его домену маршрутизации. Это ограничение и есть split DNS на практике, и именно этого чаще всего не хватает, когда клиент заявляет о его поддержке.

Прочитайте состояние, прежде чем что-либо менять:

resolvectl status
resolvectl dns tun0

Вы хотите увидеть интерфейс tun в списке с DNS-сервером и доменом маршрутизации для приватного суффикса, отображаемым как ~internal.example. Отсутствие сервера на линке означает, что клиент ничего не передал. Сервер без домена означает, что запросы либо все идут туда, либо ни один, в зависимости от клиента, и ни то ни другое не является split DNS.

Где nsswitch завершает поиск слишком рано

Стандартная строка десктопа выглядит так:

hosts: files mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] dns myhostname

mdns4_minimal обрабатывает multicast DNS и претендует на любое имя, оканчивающееся на .local. Служба Kubernetes вроде api.infra.svc.cluster.local тоже оканчивается на .local. Когда модуль mDNS сообщает NOTFOUND, действие [NOTFOUND=return] завершает весь поиск вместо перехода к модулю dns, поэтому имя так и не уходит в resolver VPN. dig полностью обходит файл, и именно поэтому два инструмента расходятся.

Это самый резкий контраст в этой проблеме, и он сводится к одному слову в конфигурационном файле, который старше вашего VPN. Если изменить действие на [NOTFOUND=continue], mDNS сохранится для принтеров и локальных устройств, а DNS сможет увидеть имя. Замена строки на hosts: files dns тоже работает и грубее: она отбрасывает разрешение .local для принтера, которым вы реально пользуетесь.

Имена, которые живут ровно в одной сети

Приватные зоны авторитетны ровно в одном месте. Имя AWS вроде ip-10-0-4-31.eu-west-1.compute.internal разрешается через resolver VPC того региона, и только пока ваша конечная точка VPN находится в том же регионе, что и ресурс. Подключитесь к конечной точке во Франкфурте, запросите имя compute.internal из Ирландии, и никакие локальные настройки не помогут: зона, содержащая запись, оттуда недоступна.

Домены поиска дают обратную картину. Голое service дополняется тем суффиксом, который resolver считает применимым, и при линке VPN локальный домен может победить корпоративный. Запрашивайте с точкой в конце, чтобы убрать догадки, getent ahosts service.internal.example., и если это работает, а короткое имя нет, исправление — домен поиска на туннеле, а не адрес сервера.

Управляемые клиенты добавляют третий уровень над системным resolver. Профиль VPN в Windows может нести собственную политику разрешения имён, правила для отдельных приложений и таблицу NRPT, которая действует даже при выключенном туннеле. Приложения с собственным кэшем DNS, обычные для сред .NET и Java, долго хранят устаревший сбой после того, как цепочка исправлена. Клиенты Apple настраивают split DNS через совпадающие домены в настройках DNS, а не через resolv.conf, а Chromium или Firefox в режиме защищённого DNS могут обходить системный resolver для публичных имён. Справочник Microsoft по разрешению имён VPN описывает сторону Windows этого уровня политики.

Решите, какой уровень исправлять

Идите от симптома к уровню, а не пробуйте исправления по порядку:

SymptomWhat it meansCheck first
dig answers, getent failslookup chain, not the packet pathnsswitch line and resolvectl status
Timeout rather than NXDOMAINprivate resolver unreachable through the tunnelresolvectl dns tun0, route to that address
NXDOMAINan authority answered and does not know the namezone owner, endpoint region
FQDN works, short name failssearch suffix mismatchresolvectl domain on the tun link
getent works, app failsapp cache or its own resolverrestart the app, check secure DNS

Две строки несут основную тяжесть. Тайм-аут — это проблема доступности: приватный resolver не отвечает через туннель, поэтому проверьте маршрут до его адреса и то, тот ли DNS-сервер на линке, о котором вы думаете. NXDOMAIN — это проблема авторитета: что-то ответило и не знает имени, что указывает на зону, регион или resolver, который никогда не должен был получать этот запрос.

Последняя строка — та, которую люди пропускают, потому что обе половины выглядят как успех. Короткое имя, разрешающееся иначе, чем FQDN, — это несовпадение суффикса, и повторное подключение туннеля не изменит того, какой суффикс передаёт клиент.

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

resolvectl status > before.txt
getent ahosts service.internal.example >> before.txt

Выполняйте проверки в этом порядке и останавливайтесь на первой неудачной. getent и resolvectl query должны совпадать; если getent не работает, а dig отвечает, оставайтесь в цепочке поиска и не трогайте маршрутизацию. resolvectl status должен показывать DNS-сервер и домен маршрутизации на интерфейсе tun. nsswitch не должен преждевременно возвращаться с NOTFOUND для проверяемого суффикса. Имя должно быть авторитетным в сети, к которой подключена ваша конечная точка, проверено с точкой в конце. Приложение не должно хранить собственный кэш или переопределение защищённого DNS.

Исправляйте только первый отказавший уровень, затем снова выполните те же команды. Два изменения сразу не дают понять, какое из них имело значение, и следующая авария начинает разбор с нуля.

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