VPN на Android без root: кому принадлежат согласие, маршруты и блокировка
Согласие, интерфейс TUN и политика блокировки системы — это три разных разрешения. Определите, какой компонент владеет каждым из них, прежде чем оценивать VPN-приложение для Android.
Root — не то, что позволяет VPN-приложению на Android передавать трафик, блокировать утечки или переживать перезагрузку. Люди сводят всё это к фразе «у приложения есть разрешение VPN», а она скрывает три отдельных механизма: согласие пользователя, пакетный интерфейс, который создаёт система, и политику блокировки платформы, которую не приложению определять. Оценивать дизайн VPN на Android — значит спрашивать, на что из этих трёх на самом деле опирается утверждение, потому что у каждого свой владелец и свой режим отказа.
Согласие приходит через системную активность
VpnService.prepare() ничего не выдаёт. Он возвращает intent в системную активность согласия, когда согласия нет, и приложение запускает этот диалог, чтобы пользователь одобрил сервис. Запуск собственного процесса приложения, добавление разрешения в манифест или показ собственного экрана «включить VPN» не создают согласие.
У согласия есть и область действия. Оно принадлежит пакету в профиле пользователя, поэтому личная копия и рабочая копия — разные решения, и владелец устройства может подготовить управляемый профиль иначе, чем личный. Если диалог никогда не появляется, а prepare() возвращает null, согласие для этого пакета в этом профиле уже есть; это не доказательство надёжности приложения, а лишь то, что разрешение на месте.
Интерфейс — это запрос, который выполняет система
После согласия приложение описывает желаемое с помощью VpnService.Builder: адреса, маршруты, серверы DNS, MTU и, где поддерживается, приложения для включения или исключения. establish() просит систему создать интерфейс туннеля и направить в него подходящий трафик. Решение о маршрутизации принадлежит системе; приложение получает файловый дескриптор и затем должно сделать с пакетами что-то полезное.
| What you observe | Who actually owns it |
|---|---|
| VPN consent dialog | The system, triggered by prepare() |
| Routes, DNS and app lists | The app requests them; the system applies them |
| One active VPN per profile | The platform; a new service replaces the old one |
| Always-on and block-without-VPN | Device policy enforced by the platform |
Эта таблица — архитектура в одном месте. Приложение участвует в обработке пакетов; ему не принадлежат интерфейс, таблица маршрутизации или число служб, которые могут работать.
Сокет туннеля должен выбраться из собственного туннеля
Соединение, которое несёт зашифрованный трафик, само является сокетом. Если его направить обратно в интерфейс, только что созданный приложением, туннель зависит от туннеля и останавливается. VpnService.protect() привязывает этот сокет к нижележащей сети, чтобы он обходил перехват, и поэтому корректный клиент защищает свой внешний сокет до первого рукопожатия.
adb shell dumpsys connectivity | grep -i -A5 vpn
adb shell settings get global always_on_vpn_app
adb shell settings get global always_on_vpn_lockdown
Эти команды показывают установленную сеть VPN, пакет постоянного включения и задана ли блокировка. Это наблюдения только для чтения: если значения не совпадают с тем, что утверждает дизайн, утверждение касается интерфейса приложения, а не состояния системы.
Блокировка может принадлежать платформе
Android документирует политику, которая блокирует соединения, не использующие VPN. Это исполнение — контракт платформы, а не приложение, которое в цикле повторяет попытки соединения. Переключатель находится в настройках VPN устройства как «Блокировать соединения без VPN», а на управляемом устройстве владелец может включить его для всего профиля. Различие важно при оценке маркетингового утверждения: клиент может показывать значок замка и агрессивно переподключаться, пока прямой трафик всё ещё возможен, либо охваченные приложения могут оставаться офлайн, потому что система применяет блокировку.
Значок замка рисует приложение, поэтому он доказывает лишь то, что приложение считает себя подключённым. Потеряет ли другое приложение доступ к сети, решает платформа, поэтому полезная проверка — увидеть, как это приложение отказывает при активном туннеле.
Ярлык «постоянно включено» из магазина приложений тоже не заменяет этот контракт. Постоянное включение и блокировка — это настройки устройства, которые часто применяет владелец, и их поведение для исключённых приложений определяет платформа. Читайте настройку, а не значок.
Четыре вопроса для обзора дизайна
Во-первых, кто дал согласие: какой пакет, какой профиль и был ли это пользователь или политика управляемого устройства? Во-вторых, что запросило приложение: какие маршруты, какие семейства адресов, какие приложения включены или исключены? В-третьих, что происходит, когда туннель недоступен: система блокирует или трафик возвращается в нижележащую сеть? В-четвёртых, что приложение делает с полученными пакетами?
Четвёртый вопрос — тот, на который модель разрешений ответить не может. Согласие и интерфейс TUN доказывают, что приложение может принимать трафик, а не то, что оно его шифрует, аутентифицирует партнёра или чисто восстанавливается. Это свойства реализации, и именно там честный обзор проводит своё время.
Лишний запрос root не становится законным от того, что результат называют аварийным выключателем, а диалог согласия — не аудит безопасности. Держите три разрешения раздельно, и каждое утверждение станет проверяемым.