Android предоставляет разработчикам API для создания решений виртуальной частной сети (VPN). В этом документе рассказывается, как разработать и протестировать собственный VPN-клиент для устройств Android.
Обзор
VPN позволяет устройствам, которые физически не находятся в сети, безопасно подключаться к ней.
В Android есть встроенный клиент VPN (PPTP и L2TP/IPSec), который иногда называют устаревшим VPN. В Android 4.0 (уровень API 14) появились API, позволяющие разработчикам приложений создавать собственные VPN-решения. Вы упаковываете свое VPN-решение в приложение, которое пользователи устанавливают на устройство. Обычно разработчики создают VPN-приложения по одной из следующих причин:
- Чтобы использовать протоколы VPN, которые не поддерживаются встроенным клиентом.
- Вы хотите, чтобы пользователи могли подключаться к виртуальным частным сетям без затруднительных настроек.
В оставшейся части этого документа объясняется, как разрабатывать приложения VPN (включая постоянные и для отдельных приложений), и не рассматривается встроенный клиент VPN.
Удобство использования
В Android есть пользовательский интерфейс, который помогает настраивать, запускать и останавливать VPN-решение. Кроме того, в системном интерфейсе показывается уведомление об активном VPN-подключении. В Android есть следующие элементы интерфейса для VPN-подключений:
- Перед первым запуском VPN-приложения система показывает диалоговое окно с запросом на подключение. В диалоговом окне пользователю устройства будет предложено подтвердить, что он доверяет VPN, и принять запрос.
- На экране настроек VPN ("Настройки > Сеть и интернет > VPN") показаны приложения VPN, для которых пользователь принял запросы на подключение. Есть кнопка для настройки системных параметров или удаления VPN.
- Когда подключение активно, на панели быстрых настроек показывается информационная панель. Если нажать на ярлык, откроется диалоговое окно с дополнительной информацией и ссылкой на настройки.
- В строке состояния появится значок VPN (ключ), который указывает на активное подключение.
В приложении также должен быть интерфейс, позволяющий пользователю настраивать параметры сервиса. Например, вашему решению может потребоваться получить настройки аутентификации аккаунта. Добавьте в приложение следующие элементы интерфейса:
- Элементы управления для запуска и остановки подключения вручную. Постоянная сеть VPN может подключаться по мере необходимости, но при первом использовании VPN пользователям нужно будет настроить подключение.
- Уведомление, которое нельзя закрыть, когда сервис активен. В уведомлении может показываться статус подключения или дополнительная информация, например статистика сети. При нажатии на уведомление приложение открывается в активном режиме. Удалите уведомление, когда сервис станет неактивным.
VPN-сервис
Приложение подключает системную сеть пользователя (или рабочего профиля) к VPN-шлюзу. Каждый пользователь (или рабочий профиль) может запустить свое VPN-приложение. Вы создаете VPN-сервис, который система использует для запуска и остановки VPN, а также для отслеживания статуса подключения. Ваш VPN-сервис наследует класс VpnService.
Сервис также выступает в качестве контейнера для подключений шлюза VPN и их локальных интерфейсов устройств. Экземпляр сервиса вызывает методы VpnService.Builder, чтобы создать новый локальный интерфейс.
VpnService подключает сеть Android к шлюзу VPNВаше приложение передает следующие данные, чтобы подключить устройство к VPN-шлюзу:
- Считывает исходящие IP-пакеты из дескриптора файла локального интерфейса, шифрует их и отправляет на шлюз VPN.
- Записывает входящие пакеты (полученные и расшифрованные из VPN-шлюза) в дескриптор файла локального интерфейса.
Для каждого пользователя или профиля может быть только один активный сервис. При запуске нового сервиса активный сервис автоматически останавливается.
Добавить сервис
Чтобы добавить VPN-сервис в приложение, создайте сервис Android, наследующий от VpnService. Объявите сервис VPN в файле манифеста приложения, добавив следующее:
- Защитите сервис с помощью разрешения
BIND_VPN_SERVICE, чтобы к нему могла подключаться только система. - Рекламируйте сервис с помощью фильтра интентов
android.net.VpnService, чтобы система могла найти ваш сервис.
Ниже показано, как объявить сервис в файле манифеста приложения.
<service android:name=".MyVpnService"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
Теперь, когда ваше приложение объявило сервис, система может автоматически запускать и останавливать VPN-сервис вашего приложения по мере необходимости. Например, система управляет вашим сервисом, когда запущена постоянная VPN.
Подготовка сервиса
Чтобы подготовить приложение к использованию в качестве текущего VPN-сервиса пользователя, вызовите VpnService.prepare(). Если пользователь устройства ещё не предоставил разрешение вашему приложению, метод возвращает намерение действия.
Используйте это намерение, чтобы запустить системное действие, запрашивающее разрешение. Появится диалоговое окно, похожее на другие окна с запросом разрешений, например на доступ к камере или контактам. Если приложение уже подготовлено, метод возвращает null.
В качестве VPN-сервиса можно использовать только одно приложение. Всегда вызывайте метод VpnService.prepare(), поскольку пользователь мог выбрать другое приложение в качестве VPN-сервиса с момента последнего вызова метода. Подробнее о жизненном цикле сервиса…
Подключить сервис
После запуска сервиса вы можете создать новый локальный интерфейс, подключенный к шлюзу VPN. Чтобы запросить разрешение и подключить сервис к шлюзу VPN, выполните следующие действия в указанном порядке:
- Вызовите метод
VpnService.prepare(), чтобы запросить разрешение (при необходимости). - Вызовите
VpnService.protect(), чтобы сокет туннеля приложения находился за пределами системной VPN и не возникало циклическое подключение. - Вызовите
DatagramSocket.connect(), чтобы подключить сокет туннеля приложения к VPN-шлюзу. - Вызовите методы
VpnService.Builder, чтобы настроить на устройстве новый локальный интерфейс TUN для VPN-трафика. - Вызовите
VpnService.Builder.establish(), чтобы система установила локальный интерфейс TUN и начала маршрутизировать трафик через него.
Обычно шлюз VPN предлагает настройки для локального интерфейса TUN во время рукопожатия. Приложение вызывает методы VpnService.Builder, чтобы настроить сервис, как показано в примере ниже.
// Configure a new interface from our VpnService instance. This must be done // from inside a VpnService. val builder = Builder() // Create a local TUN interface using predetermined addresses. In your app, // you typically use values returned from the VPN gateway during handshaking. val localTunnel = builder .addAddress("192.168.2.2", 24) .addRoute("0.0.0.0", 0) .addDnsServer("192.168.1.1") .establish()
Пример в разделе VPN для отдельных приложений показывает конфигурацию IPv6 с дополнительными параметрами. Чтобы создать новый интерфейс, необходимо добавить следующие значения VpnService.Builder:
addAddress()- Добавьте хотя бы один адрес IPv4 или IPv6 вместе с маской подсети, которую система назначает в качестве адреса локального интерфейса TUN. Обычно приложение получает IP-адреса и маски подсети от VPN-шлюза во время рукопожатия.
addRoute()- Добавьте хотя бы один маршрут, если хотите, чтобы система отправляла трафик через интерфейс VPN. Маршруты фильтруются по адресам назначения. Чтобы принимать весь трафик, задайте открытый маршрут, например
0.0.0.0/0или::/0.
Метод establish() возвращает экземпляр ParcelFileDescriptor, который приложение использует для чтения и записи пакетов в буфер интерфейса и из него. Метод establish() возвращает null, если приложение не подготовлено или кто-то отзывает разрешение.
Жизненный цикл сервиса
Приложение должно отслеживать статус выбранной в системе VPN и всех активных подключений. Обновите интерфейс приложения, чтобы пользователь устройства знал обо всех изменениях.
Запуск сервиса
Запустить VPN-сервис можно следующими способами:
- Приложение запускает сервис (обычно это происходит, когда пользователь нажимает кнопку подключения).
- Система запускает сервис, потому что включена функция Постоянная VPN.
Приложение запускает VPN-сервис, передавая намерение в startService(). Подробнее о том, как запустить службу…
Система запускает службу в фоновом режиме, вызывая метод onStartCommand(). Однако в Android 8.0 (уровень API 26) и более поздних версиях на фоновые приложения накладываются ограничения. Если вы поддерживаете эти уровни API, вам нужно перевести сервис в активный режим, вызвав Service.startForeground(). Подробнее о том, как запустить службу в активном режиме…
Остановка сервиса
Пользователь устройства может остановить сервис в интерфейсе приложения. Остановите сервис, а не просто закройте подключение. Система также останавливает активное подключение, когда пользователь устройства выполняет следующие действия на экране VPN в приложении "Настройки":
- отключается от VPN или удаляет его;
- отключает постоянную VPN для активного подключения
Система вызывает метод onRevoke() вашего сервиса, но этот вызов может произойти не в основном потоке. Когда система вызывает этот метод, трафик уже маршрутизируется через альтернативный сетевой интерфейс. Вы можете безопасно утилизировать
следующие ресурсы:
- Закройте сокет защищенного туннеля к шлюзу VPN, вызвав
DatagramSocket.close(). - Закройте дескриптор файла ParcelFileDescriptor, вызвав метод
ParcelFileDescriptor.close().
Постоянная VPN
Вы можете задать настройки, при которых VPN-сервис будет автоматически активироваться при загрузке устройства Android и функционировать до отключения этого устройства. Эта функция называется Постоянная VPN и доступна в Android 7.0 (уровень API 24) и более поздних версиях. Android поддерживает жизненный цикл сервиса, но за подключение к VPN-шлюзу отвечает сам VPN-сервис. Постоянная VPN также может блокировать подключения, которые не используют VPN.
Удобство использования
В Android 8.0 и более поздних версиях система показывает следующие диалоговые окна, чтобы сообщить пользователю устройства о постоянно включенной VPN:
- Если постоянное VPN-подключение прервется или не будет установлено, появится уведомление, которое нельзя закрыть. Если нажать на уведомление, откроется диалоговое окно с подробной информацией. Уведомление исчезнет, когда VPN-подключение будет восстановлено или кто-то отключит параметр "Постоянная VPN".
- Постоянная сеть VPN позволяет пользователю устройства блокировать любые сетевые подключения, которые не используют VPN. При включении этого параметра в приложении "Настройки" появляется предупреждение о том, что до подключения к VPN у пользователя не будет доступа к интернету. В приложении "Настройки" пользователю будет предложено продолжить или отменить действие.
Поскольку постоянное подключение запускается и останавливается системой, а не пользователем, вам необходимо адаптировать поведение и пользовательский интерфейс приложения.
- Отключите все элементы интерфейса, которые прерывают подключение, поскольку им управляет система и приложение "Настройки".
- Сохраняйте конфигурацию между запусками приложения и настраивайте подключение с последними настройками. Поскольку система запускает приложение по запросу, пользователь устройства не всегда может захотеть настроить подключение.
Вы также можете использовать управляемые конфигурации, чтобы настроить подключение. Управляемые конфигурации позволяют системному администратору настраивать VPN удаленно.
Обнаружение всегда включено
В Android нет API, позволяющих подтвердить, что система запустила ваш VPN-сервис. Но если ваше приложение помечает все запущенные им экземпляры службы, вы можете предположить, что система запустила непомеченные службы для постоянно включенного VPN. Рассмотрим пример.
- Создайте экземпляр
Intent, чтобы запустить VPN-сервис. - Пометьте VPN-сервис, добавив дополнительный параметр в намерение.
- В методе
onStartCommand()сервиса найдите флаг в дополнительных параметрах аргументаintent.
Заблокированные подключения
Пользователь устройства или системный администратор может настроить принудительную передачу всего трафика через VPN. Система блокирует весь сетевой трафик, который не использует VPN. Пользователи устройства могут найти переключатель Блокировать соединения без VPN на панели параметров VPN в настройках.
Как отключить режим "Всегда включено"
Если ваше приложение не поддерживает постоянную VPN, вы можете отключить ее (в Android 8.1 (API уровня 27) или более поздней версии), задав для метаданных сервиса SERVICE_META_DATA_SUPPORTS_ALWAYS_ON значение false. В примере манифеста приложения ниже показано, как добавить элемент метаданных.
<service android:name=".MyVpnService"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
<meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
android:value=false/>
</service>
Если приложение не поддерживает постоянную VPN, система отключает элементы управления в настройках.
Контроль трафика VPN для приложений
VPN-приложения могут фильтровать, каким установленным приложениям разрешено отправлять трафик через VPN-подключение. Вы можете создать белый или черный список, но не оба сразу. Если вы не создадите списки разрешенных или запрещенных приложений, система будет направлять весь сетевой трафик через VPN.
Ваше VPN-приложение должно задать списки до установки подключения. Если вам нужно изменить списки, установите новое VPN-подключение. Приложение должно быть установлено на устройстве, когда вы добавляете его в список.
// The apps that will have access to the VPN. val appPackages = arrayOf( "com.android.chrome", "com.google.android.youtube", "com.example.a.missing.app" ) // Loop through the app packages in the array and confirm that the app is // installed before adding the app to the allowed list. val builder = Builder() for (appPackage in appPackages) { try { packageManager.getPackageInfo(appPackage, 0) builder.addAllowedApplication(appPackage) } catch (e: PackageManager.NameNotFoundException) { // The app isn't installed. } } // Complete the VPN interface config. val localTunnel = builder .addAddress("2001:db8::1", 64) .addRoute("::", 0) .establish()
Разрешенные приложения
Чтобы добавить приложение в белый список, вызовите VpnService.Builder.addAllowedApplication(). В таком случае только приложения из этого списка смогут использовать трафик VPN.
Все прочие приложения, которых нет в списке, используют системные сети так, как если бы VPN не работал. Если список пуст, то все приложения используют VPN.
Список приложений без доступа к VPN
Чтобы добавить приложение в черный список, вызовите VpnService.Builder.addDisallowedApplication().
Запрещенные приложения используют системные сети, как если бы VPN не была запущена. Все остальные приложения используют VPN.
Обход VPN
Вы можете разрешить приложениям обходить VPN и выбирать собственную сеть. Чтобы обойти VPN, вызовите VpnService.Builder.allowBypass() при создании интерфейса VPN. После запуска VPN-сервиса изменить это значение нельзя. Если приложение не привязывает свой процесс или сокет к определенной сети, сетевой трафик приложения продолжает проходить через VPN.
Приложения, привязанные к определенной сети, не имеют подключения, когда кто-то блокирует трафик, который не проходит через VPN. Чтобы направить трафик через определенную сеть, приложения вызывают такие методы, как ConnectivityManager.bindProcessToNetwork() или Network.bindSocket(), прежде чем подключить сокет.
Пример кода
В Android Open Source Project есть пример приложения под названием ToyVPN. Это приложение показывает, как настроить и подключить VPN-сервис.