Чому resolv.conf показує DNS вашого VPN, а запити його ігнорують
На хості з resolved файл — це подання, а не сам стан. Простежте за stub, серверами кожного з'єднання та рядком nsswitch, щоб побачити, що запит справді використовує.
У журналах ніщо не виглядає неправильно: інтерфейс піднятий, резолвер повідомляє 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, домени маршрутизації охоплюють лише внутрішні суфікси, а важлива програма розв'язує внутрішнє ім'я, тоді як публічне ім'я далі йде локальним шляхом. Один успішний запит доводить менше, ніж ця узгодженість.
Потім відключіться і повторіть обидва запити. Клієнт, який залишає свої домени зареєстрованими після розриву, створює машину, яка через години не справляється з внутрішніми запитами, без відкритого тунелю і без очевидної причини.