Ваш ноутбук у 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 тунелю. 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.

Виправте лише перший шар, що не вдається, а потім знову виконайте ті самі команди. Дві зміни одразу не дають змоги сказати, яка мала значення, і наступний збій починає розслідування з нуля.

Схожі статті