На Android приложение часто узнает о VPN не по косвенным сетевым следам, а напрямую от самой системы. Программа запрашивает сведения о текущем подключении, а Android честно сообщает, что поверх Wi-Fi или мобильной сети работает VPN. Поэтому идея «спрятать VPN, но оставить его для той же программы» упирается не в адрес сервера и не в настройку клиента, а в устройство самого Android.
Короткий вывод такой. На обычном Android без вмешательства в систему нельзя сохранить VPN для конкретного приложения и одновременно сделать так, чтобы то же приложение не увидело VPN. Зато можно сделать другое, штатное и безопасное действие: вывести нужное приложение из туннеля, чтобы приложение работало так, как будто VPN нет.
Как Android через ConnectivityManager и NetworkCapabilities сообщает приложению о VPN
Механика довольно простая. Приложение получает активную сеть через класс ConnectivityManager, затем запрашивает у системы описание свойств сети через NetworkCapabilities и смотрит, какие признаки у подключения выставлены. В документации Android прямо указано, что одна и та же сеть может одновременно содержать несколько транспортов, например Wi-Fi, мобильную сеть и VPN. Там же описаны системные признаки TRANSPORT_VPN и NET_CAPABILITY_NOT_VPN, по которым приложение понимает, идет ли трафик через VPN. Android также позволяет не только разово спросить состояние сети, но и подписаться на изменения и получать уведомления при смене свойств подключения. Подробно логика работы описана в документации Android.
val cm = getSystemService(ConnectivityManager::class.java)
val active = cm.activeNetwork ?: return
val caps = cm.getNetworkCapabilities(active) ?: return
val vpnDetected =
caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN) ||
!caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
if (vpnDetected) {
// приложение понимает, что работает через VPN
}
Здесь есть важная тонкость. Когда разработчик строит собственный запрос к сети, Android по умолчанию добавляет в такой запрос несколько обычных признаков, включая NET_CAPABILITY_NOT_VPN. Из-за этого часть кода по умолчанию видит только обычные сети, пока разработчик явно не изменит запрос. Но когда приложение проверяет именно активную сеть или подписывается на уведомления о смене сети, системный признак VPN снова оказывается на виду.
Почему на штатном Android нельзя «спрятать VPN, но не выключать его»
Потому что для Android VPN не маскируется под обычный Wi-Fi. VPN для системы остается отдельным видом транспорта со своими признаками. Android не считает такую видимость ошибкой. Платформа специально дает приложению модель сети, где видны и базовое подключение, и туннель поверх него. Пока приложение использует системный сетевой стек обычным способом, платформа не обязана делать вид, что VPN не существует.
Отсюда и появляется много мифов. Смена DNS, другой сервер, новый адрес, другой протокол туннеля или «менее заметный» клиент не решают проблему на уровне ConnectivityManager. Такие действия могут менять картину на стороне удаленного сервиса, но не убирают системный признак VPN из NetworkCapabilities. Поэтому советы вроде «поставьте другой клиент, и Android перестанет показывать VPN» обычно не работают.
Что реально работает без серых обходов
Рабочий путь один. Не пытаться скрыть VPN от приложения, а вывести приложение из туннеля. Android поддерживает такую схему штатно через списки разрешенных и исключенных приложений в VpnService. Документация прямо говорит, что исключенные приложения используют системную сеть так, как будто VPN не запущен. По сути речь идет о раздельной маршрутизации по приложениям, когда часть программ работает через туннель, а часть идет напрямую. Про такой подход говорится в документации Android по VPN.
val builder = VpnService.Builder()
builder.addDisallowedApplication("com.example.targetapp")
// дальше VPN поднимается для остальных приложений
У такого решения есть честная цена. Исключенное приложение больше не пользуется VPN вообще. Трафик, DNS, сетевой адрес и ограничения сети для такого приложения возвращаются к обычному системному подключению. Для банковского приложения, видеосервиса, корпоративного клиента или игры такой подход часто и нужен. Для защиты приватности именно этого приложения такой подход уже не подходит.
Есть и еще одна тонкость. Метод allowBypass() в VpnService не превращает VPN в невидимку. Метод лишь разрешает приложениям обходить VPN и выбирать собственную сеть, если разработчик приложения сам привязывает процесс или сокет к нужному подключению. Без такого поведения трафик по-прежнему идет через VPN. Поэтому allowBypass() нельзя считать кнопкой «сделать VPN незаметным для всех программ».
Где начинается серая зона
Когда человеку нужен именно сценарий «приложение продолжает работать через туннель, но при этом не видит VPN», разговор быстро уходит в сторону прав суперпользователя, перехвата вызовов системы, подмены ответов Android, модулей для изменения поведения программ, нестандартных прошивок и ручного вмешательства в сетевой стек. Технически такие действия уже не похожи на обычную настройку телефона. Такие действия ломают модель доверия платформы, часто приводят к нестабильности, конфликтуют с обновлениями и выходят на территорию обхода ограничений. Для безопасной публикации такой путь не годится.
Материал нужен для понимания сетевой модели Android, настройки совместимости приложений и штатного раздельного вывода программ из туннеля. Использовать такие знания для обхода ограничений сервисов, сокрытия факта применения VPN, нарушения условий использования приложений или требований закона, включая требования РФ, нельзя.
Что делать на практике, если приложение не любит VPN
| Задача | Что получится на деле | Что делать |
|---|---|---|
| Оставить VPN для всех приложений и скрыть его от одного приложения | На обычном Android не получится | Не тратить время на «волшебные» настройки клиента |
| Оставить VPN для большинства приложений, а выбранному приложению дать обычную сеть | Получится | Включить раздельную маршрутизацию или исключить приложение из VPN |
| Уменьшить число проблем с банковским, стриминговым или корпоративным приложением | Часто получается | Сначала исключить приложение из туннеля и проверить работу на обычной сети |
| Сделать аккуратную корпоративную схему VPN только для части приложений | Получится, если клиент поддерживает списки приложений | Настроить нужные программы на стороне VPN-клиента |
Что часто путают в обсуждениях про VPN на Android
Вокруг темы обычно смешивают две разные задачи. Первая задача такая: скрыть VPN от удаленного сервиса на серверной стороне. Вторая задача такая: скрыть VPN от самого Android-приложения на устройстве. Первая задача может зависеть от сетевого адреса, DNS, особенностей трафика, поведения клиента и других сетевых признаков. Вторая задача упирается в то, что Android сам сообщает приложению о наличии VPN. Поэтому даже идеально «чистый» трафик снаружи не отменяет факт, что внутри телефона приложение получает сведения о туннеле от системы.
Еще одна частая ошибка связана с предположением, что если VPN-клиент показывает только значок в строке состояния, то проблема чисто визуальная. На деле значок лишь отражает состояние, которое уже знает система. Приложение не обязано смотреть на значок. Приложение получает сведения о сети через системные классы и уже по ним принимает решение.
Вывод
Если смотреть на Android без форумных легенд, картина довольно трезвая. ConnectivityManager и NetworkCapabilities не «угадывают» VPN как внешний наблюдатель, а просто показывают приложению ту модель сети, которую знает сама система. Поэтому на штатном Android нельзя одновременно сохранить VPN для конкретного приложения и скрыть VPN от того же приложения. Реальный, чистый и поддерживаемый вариант только один: исключить нужное приложение из туннеля и дать ему обычную сеть. Если задача требует именно скрыть сам факт VPN при сохранении туннеля, задача уже лежит за пределами обычной настройки Android и за пределами безопасной эксплуатации устройства.
FAQ
Может ли приложение увидеть VPN без анализа сетевого адреса?
Да. Во многих случаях приложению хватает системных сведений о сети через ConnectivityManager и NetworkCapabilities.
Поможет ли другой протокол VPN или другой сервер?
Для системного признака VPN обычно нет. Протокол меняет сетевое поведение наружу, но не убирает TRANSPORT_VPN внутри Android.
Раздельная маршрутизация скрывает VPN от приложения?
Не совсем. Раздельная маршрутизация выводит приложение из VPN. После исключения приложение работает через обычную сеть и поэтому не видит активный туннель в своем сетевом пути.
Можно ли решить задачу без прав суперпользователя?
Для сценария «оставить VPN и одновременно скрыть VPN от того же приложения» на штатном Android ответ отрицательный. Для сценария «исключить приложение из туннеля» права суперпользователя не нужны.
Когда такой подход действительно полезен?
Когда конкретное приложение ломается из-за VPN, а остальная система должна продолжать работать через туннель.