Читайте Android DNS правильно: по сети, а не в resolv.conf
Ваше приложение для Android разрешает имена через resolver для каждой сети, а не через resolv.conf, который вы только что прочитали. Сначала проверьте LinkProperties, привязки сокетов и Private DNS.
Вы получаете shell на устройстве, читаете /etc/resolv.conf или выкапываете старое значение net.dns1, и получаете один адрес. Приложение перед вами разрешает имена совсем через другое. В текущей версии Android resolver существует для каждой сети: система держит политику для каждого Network и применяет её к сокету, отправившему запрос. Файл и старое свойство — остатки, и ни то ни другое не описывает путь, который ваш запрос действительно прошёл.
Этот разрыв проявляется при внедрении split-tunnel и Private DNS, где два приложения на одном телефоне могут законно видеть разные ответы. Это руководство начинается с неверного или отсутствующего разрешения и идёт назад к конфигурации, которой оно принадлежит.
Resolver принадлежит сети, а не устройству
Разрешение имён в Android 9 и новее идёт через системный resolver, который опирается на Network, к которой привязан сокет. Каждая сеть несёт свои собственные LinkProperties, и эти свойства содержат список DNS-серверов, домены поиска и состояние Private DNS только для этой сети. Ничто в этой модели не соответствует единому списку на всё устройство, который можно прочитать из файла.
VPN делает разделение очевидным. Когда туннель поднимается, приложение VPN передаёт DNS-серверы, зарегистрированные в собственной сети VPN, поэтому приложения, направленные через него, разрешают имена через эти серверы, а исключённые приложения продолжают использовать нижележащую сеть. Одно устройство, два пути ответа, никакого противоречия.
Прочитайте снимок изнутри затронутого приложения
Спросите само приложение. Метод, у которого есть Context и обычное разрешение android.permission.ACCESS_NETWORK_STATE, может прочитать снимок, не трогая системные свойства:
val cm = getSystemService(ConnectivityManager::class.java) ?: return
val network = cm.activeNetwork
val links = network?.let { cm.getLinkProperties(it) }
val dnsServers = links?.dnsServers
val privateDnsActive = links?.isPrivateDnsActive
val privateDnsName = links?.privateDnsServerNameactiveNetwork — это сеть по умолчанию для вашего процесса, а не единственная сеть на устройстве, и она может быть null, когда у вызывающего нет пригодной сети по умолчанию. Заблокированная VPN по умолчанию — обычная причина. isPrivateDnsActive сообщает, идут ли запросы в этой сети через TLS к именованному resolver, что меняет то, что покажет простая проба UDP 53. Поверхность API описана в LinkProperties, и каждое значение в ней — снимок конфигурации, а не доказательство, что конкретный запрос дошёл до конкретного сервера.
Когда сеть по умолчанию — не та, что выполняет работу
Снимок отвечает только за сеть по умолчанию. Приложение может привязать свой процесс или один сокет к другой сети, и тогда разрешение следует этому выбору, пока ваша диагностика по-прежнему сообщает о сети по умолчанию. Браузер с собственным DNS-over-HTTPS resolver вообще не обращается к системному списку, поэтому его ответы ничего не говорят об остальном устройстве. Приложение рабочего профиля работает под отдельным пользователем со своей политикой. Проверьте привязку, прежде чем винить серверы.
Интеграция с VPN — вторая половина. Получить строку dhcp-option DNS в профиле OpenVPN и зарегистрировать этот адрес в сети VPN Android — это два отдельных шага, и отсутствующий маршрут к серверу даёт сбой так же тихо, как неверный адрес. С машины разработчика adb shell dumpsys connectivity печатает те же свойства связи по сети, когда сборка это позволяет, что подтверждает список без написания кода.
Сравните конфигурацию с одним разрешением, которое вам разрешено
Выберите имя, которое должно разрешаться в частном пространстве имён, и одно публичное имя, и запишите оба результата рядом со снимком: сеть, настроенные серверы, состояние Private DNS и точный текст ошибки. Прямой запрос к именованному серверу доказывает, что этот сервер вам отвечает; он не доказывает, что обычное разрешение приложения выбрало его. Ответ из кэша — не свежий тест, поэтому очистите кэш или используйте имя, которое сегодня никто не запрашивал.
Если resolver обслуживает только клиентов из определённой сети доступа, то, что он видит после выхода из VPN, может решить исход, и это вопрос политики для того, кто им управляет, а не доказательство, что операторы блокируют UDP 53. Там, где Private DNS активен, неквалифицированное имя хоста может быть отклонено по замыслу, пока тот же сервер нормально отвечает на публичные имена. Чтение свойств говорит вам, какой режим действует, так что вы перестаёте гадать о фильтрах пакетов.
Исправьте слой, которому принадлежит несоответствие
Работайте вверх, а не редактируйте глобальное состояние. Если приложение привязано к неверной сети, исправьте привязку. Если профиль передаёт неверную опцию DNS, исправьте профиль. Если Private DNS строг в сети, resolver которой не может обслуживать вашу частную зону, это решение управления устройством, а не повод отключать функцию везде.
Проталкивание публичных resolver-ов в DHCP-хук или системное свойство — это исправление, которое постоянно вызывает инциденты: частные имена перестают разрешаться, изменение переживает сессию VPN, и следующий человек на устройстве находит устаревший адрес без владельца. Держите изменение внутри VPN, приложения или слоя управления, которому принадлежит несоответствие, затем перечитайте снимок, чтобы подтвердить, что список серверов действительно изменился.
В конце вы хотите один запрос приложения, привязанный к одной сети, списку resolver-ов и состоянию Private DNS. Как только вы можете показать эту цепочку, знакомый адрес в файле перестаёт быть доказательством чего-либо.