VPN для отдельных приложений в Android: исключить приложение — не значит заблокировать его
Список приложений на экране принадлежит одному владельцу туннеля, и исключение приложения направляет его в обход этого туннеля, а не отключает от сети. Начните оттуда.
Два человека задают один и тот же вопрос и получают несовместимые ответы, потому что стоят в разных местах. У одного открыт экран системных настроек VPN, и он редактирует список приложений. Другой пишет VpnService.Builder.addDisallowedApplication() в редакторе. Оба называют это VPN-фильтром по приложениям. Только один из них владеет тем управлением, которое описывает, и ни один ничего не блокирует.
Начните с экрана, который видит пользователь
Откройте Настройки, найдите пункт VPN и посмотрите на подключение, которое действительно активно. Если клиент предлагает список по приложениям, этот список принадлежит собственному туннелю клиента. Платформа не публикует общий API, через который постороннее приложение могло бы читать или менять выбор приложений другого VPN, поэтому второе приложение не может исправить этот список за пользователя.
Это первая развилка в разборе. Если у установленного VPN нет такой настройки, обходить кодом нечего: функции нет в продукте, которому принадлежит туннель. Если она есть, правильное исправление находится на этом экране и делается тем, кто может его открыть.
Проследите пакет в туннель
Когда ваше приложение строит туннель, оно создаёт интерфейс TUN и объявляет, чей трафик в него попадает. Вызовы членства происходят до установки интерфейса, а после этого набор правил фиксируется на эту сессию:
val builder = VpnService.Builder()
builder.addAddress("10.8.0.2", 32)
builder.addRoute("0.0.0.0", 0)
builder.addDisallowedApplication("com.example.bypass")
val tun = builder.establish() // membership decided hereЭти вызовы относятся только к туннелю, который ваше приложение только что создало, и ни к чему больше. Они не являются политикой всего устройства, не сохраняются для других клиентов и не могут дотянуться до чужого VPN. Когда вызов конструктора выбрасывает исключение из-за того, что пакет не удаётся разрешить, интерфейс так и не устанавливается, и это чистый отказ, а не тихий.
Почему исключение выглядело как блокировка
Исключённое приложение направляется в обход вашего туннеля в нижележащую сеть. Это противоположность блокировке: трафик по-прежнему покидает устройство, просто вне интерфейса, которым вы управляете. Приложение в стиле межсетевого экрана делает наоборот. Оно впускает трафик приложения в свой туннель, а затем отбрасывает пакеты или отказывается их пересылать, поэтому интерфейс тот же, а направление политики противоположное.
Работа за этим переворотом — не флаг конфигурации. Вы читаете IP-пакеты из дескриптора туннеля, решаете по каждому потоку, а затем пересылаете их через защищённый сокет или отбрасываете. Всё, что вы не обработали, просто останавливается, и поэтому недоделанный туннель выглядит как сломанная сеть, а не как работающий фильтр. Если цель — блокировка, закладывайте обработку пакетов, службу переднего плана и письменную политику для трафика, который вы не можете классифицировать.
Что ломается, когда вы строите это сами
Android обычно разрешает только один активный VPN одновременно, поэтому ваш фильтрующий туннель и туннель приватности конкурируют за одно место, если продукты не поддерживают связывание. Постоянный VPN с блокировкой подключений меняет характер отказа так, как здесь важно: трафик, который не может войти в туннель, отбрасывается вместо прямой отправки, чего хочет схема защиты от утечек и чего сессия отладки почти не ждёт.
Видимость пакетов — вторая ловушка. Разрешение имени пакета приложения может завершиться ошибкой NameNotFoundException, а в новых версиях Android набор видимых пакетов зависит от объявленных вами запросов. Обрабатывайте исключение вместо предположения, что любое имя разрешится, и перечитывайте список при установке или удалении приложений, а не храните его в кэше вечно.
Управляемые устройства добавляют третьего владельца. Политика корпоративной мобильности может задать VPN по приложениям или профиль постоянного подключения, и обычное приложение не может это переопределить. Проверьте, управляется ли устройство, прежде чем отлаживать свой код, потому что в этом случае список, который вы пытаетесь изменить, может быть недоступен для правки никому, кто держит телефон.
Проверьте это на приложении, которым управляете вы
Не тестируйте на приложении, которое пытаетесь фильтровать. Возьмите второе приложение, чьё сетевое поведение вы можете наблюдать, и проверьте, выходит ли его трафик через ваш туннель, тогда как трафик цели нет. Если оба остаются внутри, политика работает и цель исключена; если не выходит ни один, неверен сам туннель, а список приложений ни при чём.
Затем решите, что именно из трёх вещей вы строите. Туннель по приложениям — это API конструктора, а список является частью вашего профиля. Межсетевой экран устройства — это обработка пакетов плюс жизненный цикл, который вы можете защитить. Менять чужой VPN вообще не поддерживаемый путь, и честный ответ там состоит в том, что эта настройка принадлежит тому продукту.