Android предоставляет разработчикам API для создания решений виртуальной частной сети (VPN). Прочитав это руководство, вы узнаете, как разработать и протестировать собственный VPN-клиент для устройств Android.
Обзор
VPN позволяет устройствам, которые физически не находятся в сети, безопасно получать доступ к сети.
В Android есть встроенный клиент VPN (PPTP и L2TP/IPSec), который иногда называют устаревшим VPN. В Android 4.0 (уровень API 14) появились API, позволяющие разработчикам приложений создавать собственные VPN-решения. Вы упаковываете свое VPN-решение в приложение, которое пользователи устанавливают на устройство. Обычно разработчики создают VPN-приложения по одной из следующих причин:
- Вы используете протоколы VPN, которые не поддерживаются встроенным клиентом на Android.
- Вы хотите, чтобы пользователи могли подключаться к виртуальным частным сетям без затруднительных настроек.
В оставшейся части этого руководства объясняется, как разрабатывать приложения 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, чтобы настроить сервис, как показано в примере ниже.
Kotlin
// 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()
Java
// Configure a new interface from our VpnService instance. This must be done // from inside a VpnService. VpnService.Builder builder = new VpnService.Builder(); // Create a local TUN interface using predetermined addresses. In your app, // you typically use values returned from the VPN gateway during handshaking. ParcelFileDescriptor 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(). - Закройте дескриптор файла Parcel, вызвав
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 или более поздней версии), задав для метаданных сервиса 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-подключение. Приложение должно быть установлено на устройстве, когда вы добавляете его в список.
Kotlin
// 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()
Java
// The apps that will have access to the VPN. String[] appPackages = { "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. VpnService.Builder builder = new VpnService.Builder(); PackageManager packageManager = getPackageManager(); for (String appPackage: appPackages) { try { packageManager.getPackageInfo(appPackage, 0); builder.addAllowedApplication(appPackage); } catch (PackageManager.NameNotFoundException e) { // The app isn't installed. } } // Complete the VPN interface config. ParcelFileDescriptor 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. Чтобы направить трафик через определенную сеть, приложения вызывают методы, например ConnectivityManager.bindProcessToNetwork() или Network.bindSocket(), до подключения сокета.
Пример кода
В Android Open Source Project есть пример приложения под названием ToyVPN. В этом приложении показано, как настроить и подключить VPN-сервис.