Android-приложение не может разрешить внутреннее имя, хотя браузер самого VPN-клиента до него доходит, и та же сборка работает на другом телефоне. URL не является переменной. Переменная — это то, какой сети процессу приложения разрешено использовать для разрешения имён, и Android не гарантирует, что активный VPN — это та сеть, которую используют ваши сокеты. Выполните эти проверки по порядку и остановитесь, когда снимок resolver-а не совпадёт с вашим предположением.

Шаг 1: докажите, в какой сети находится приложение

Читайте собственное представление приложения, а не строку состояния. Из ConnectivityManager получите активную сеть, её LinkProperties, список DNS-серверов и настройку приватного DNS. Если в списке есть resolver VPN, у приложения есть путь к нему; если там resolver Wi-Fi, туннель не является сетью по умолчанию для этого процесса.

Значок VPN говорит только о том, что туннель существует. Он не говорит, что процесс приложения был привязан к нему, и Android не всегда направляет разрешение имён через сеть, которая выглядит активной. Локальные VPN-приложения, передающие трафик на другое устройство, сети Wi-Fi без интернета и сборки производителей могут оставить приложение спрашивающим resolver по умолчанию вместо серверов туннеля.

Шаг 2: запросите транспорт VPN и привяжите процесс

Когда приложение обязано использовать resolver туннеля, запросите сеть с транспортом VPN и привяжите процесс в колбэке, чтобы код не угадывал, когда появится сеть:

val request = NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
    .build()

connectivityManager.requestNetwork(request, object : NetworkCallback() {
    override fun onAvailable(network: Network) {
        val bound = connectivityManager.bindProcessToNetwork(network)
        Log.i(TAG, "bound to VPN network: " + bound)
    }
})

Вызов меняет сеть по умолчанию для всего процесса, поэтому каждый созданный после него сокет использует её. Уже существующие сокеты сохраняют сеть, в которой были созданы, поэтому пул соединений, открытый до привязки, может продолжать разрешение по старому пути. Возвращаемое значение — boolean, и результат false означает, что привязка не произошла; повторите попытку с небольшой задержкой и зафиксируйте обе попытки.

Шаг 3: ограничьте привязку тем, чему она нужна

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

Две функции resolver-а могут победить привязку без всякой ошибки. Приватный DNS в строгом режиме отправляет запросы на именованный сервер DoT вместо resolver-а сети, а приложение со своим клиентом DNS-over-HTTPS полностью обходит системный resolver. Проверьте оба, прежде чем заключать, что привязка не удалась. Запрос, который так и не доходит до DNS-сервера VPN, не появится ни в одном журнале на стороне туннеля.

Шаг 4: не доверяйте ограничению CONNECTED

NetworkType.CONNECTED в WorkManager означает, что сеть подключена, и транспорт VPN удовлетворяет этому даже тогда, когда нижележащий Wi-Fi или сотовый канал пропал. Задача выполняется, каждый запрос терпит неудачу, и политика повторов превращает это в бурю, похожую на ошибку планирования. Туннель — это сеть; ограничение никогда не было проверкой интернета.

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

Шаг 5: зафиксируйте доказательства и перепроверьте диапазон

Запишите типы транспорта выбранной сети, DNS-серверы из LinkProperties, вернула ли привязка true и точную ошибку первого запроса. Затем выполните приватное имя и публичное имя при одной и той же привязке. Эта пара показывает, использовало ли приложение resolver туннеля или молча вернулось к умолчанию устройства, и это та разница, которую ощущает пользователь.

Повторите на самой старой поддерживаемой версии Android и на одном текущем выпуске. Поведение resolver-а, из-за которого путь по умолчанию выглядит правильным, — это именно то место, где различаются устройства и версии, а сборка, проходящая только на новейшем тестовом устройстве, не проверена для диапазона, который вы поставляете. adb shell dumpsys connectivity — быстрый способ подтвердить, какие сети и возможности сообщает устройство, пока вы воспроизводите сбой.

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