VPN

Android предоставляет разработчикам API для создания решений виртуальных частных сетей (VPN). Прочитав это руководство, вы узнаете, как разработать и протестировать собственный VPN-клиент для устройств под управлением Android.

Обзор

VPN позволяют устройствам, которые физически не находятся в сети, получать безопасный доступ к сети.

Android включает встроенный VPN-клиент (PPTP и L2TP/IPSec), который иногда называют устаревшим VPN . В Android 4.0 (уровень API 14) появились API, позволяющие разработчикам приложений предоставлять свои собственные решения VPN. Вы упаковываете свое VPN-решение в приложение, которое люди устанавливают на устройство. Разработчики обычно создают VPN-приложения по одной из следующих причин:

  • Предлагать протоколы VPN, которые не поддерживает встроенный клиент.
  • Чтобы помочь людям подключиться к VPN-сервису без сложной настройки.

В оставшейся части руководства объясняется, как разрабатывать VPN-приложения (включая постоянную VPN и VPN для каждого приложения ), но не рассматривается встроенный VPN-клиент.

Пользовательский опыт

Android предоставляет пользовательский интерфейс, который поможет кому-либо настроить, запустить и остановить ваше VPN-решение. Системный пользовательский интерфейс также сообщает пользователю, использующему устройство, об активном VPN-соединении. Android отображает следующие компоненты пользовательского интерфейса для VPN-подключений:

  • Прежде чем приложение VPN станет активным в первый раз, система отображает диалоговое окно запроса подключения. В диалоговом окне пользователю, использующему устройство, будет предложено подтвердить, что он доверяет VPN, и принять запрос.
  • На экране настроек VPN («Настройки» > «Сеть и Интернет» > «VPN») отображаются приложения VPN, в которых человек принимал запросы на подключение. Есть кнопка, позволяющая настроить параметры системы или забыть о VPN.
  • На панели быстрых настроек отображается информационная панель, когда соединение активно. При нажатии на метку открывается диалоговое окно с дополнительной информацией и ссылкой на «Настройки».
  • В строке состояния имеется значок VPN (ключ), указывающий на активное соединение.

Вашему приложению также необходимо предоставить пользовательский интерфейс, чтобы человек, использующий устройство, мог настроить параметры вашего сервиса. Например, вашему решению может потребоваться сохранить настройки аутентификации учетной записи. Приложения должны отображать следующий пользовательский интерфейс:

  • Элементы управления для ручного запуска и остановки соединения. Always-on VPN может подключаться при необходимости, но позволяет людям настраивать соединение при первом использовании вашего VPN.
  • Неотключаемое уведомление, когда услуга активна. Уведомление может отображать состояние подключения или предоставлять дополнительную информацию, например статистику сети. Нажатие на уведомление выводит ваше приложение на передний план. Удалите уведомление после того, как услуга станет неактивной.

VPN-сервис

Ваше приложение подключает системную сеть пользователя (или рабочего профиля ) к VPN-шлюзу. Каждый пользователь (или рабочий профиль) может запускать отдельное приложение VPN. Вы создаете службу VPN, которую система использует для запуска и остановки VPN, а также отслеживания состояния соединения. Ваша служба VPN наследуется от VpnService .

Служба также выступает в качестве контейнера для подключений VPN-шлюзов и интерфейсов их локальных устройств. Экземпляр службы вызывает методы VpnService.Builder , чтобы установить новый локальный интерфейс.

Рисунок 1. Как VpnService подключает сеть Android к VPN-шлюзу
Схема блок-архитектуры, показывающая, как VpnService создает локальный интерфейс TUN в системной сети.

Ваше приложение передает следующие данные для подключения устройства к 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-шлюз, вам необходимо выполнить действия в следующем порядке:

  1. Вызовите VpnService.prepare() , чтобы запросить разрешение (при необходимости).
  2. Вызовите VpnService.protect() чтобы сохранить туннельный сокет вашего приложения за пределами системной VPN и избежать циклического соединения.
  3. Вызовите DatagramSocket.connect() , чтобы подключить туннельный сокет вашего приложения к VPN-шлюзу.
  4. Вызовите методы VpnService.Builder , чтобы настроить новый локальный интерфейс TUN на устройстве для VPN-трафика.
  5. Вызовите 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()

Ява

// 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() .
  • Закройте дескриптор файла участка (вам не нужно его сливать), вызвав ParcelFileDescriptor.close() .

Всегда включенный VPN

Android может запускать службу VPN при загрузке устройства и поддерживать ее работу, пока устройство включено. Эта функция называется Always-On VPN и доступна в Android 7.0 (уровень API 24) или выше. Хотя Android поддерживает жизненный цикл службы, именно ваша служба VPN отвечает за соединение VPN-шлюза. Always-on VPN также может блокировать соединения, которые не используют VPN.

Пользовательский опыт

В Android 8.0 или более поздней версии система отображает следующие диалоговые окна, чтобы пользователь, использующий устройство, знал о постоянно включенном VPN:

  • Когда постоянно включенные VPN-соединения отключаются или не могут подключиться, люди видят уведомление, которое невозможно закрыть. При нажатии на уведомление открывается диалоговое окно, в котором объясняется дополнительная информация. Уведомление исчезает, когда VPN повторно подключается или кто-то отключает опцию «постоянно включенный VPN».
  • Always-on VPN позволяет пользователю устройства блокировать любые сетевые подключения, которые не используют VPN. При включении этой опции приложение «Настройки» предупреждает людей об отсутствии подключения к Интернету до подключения VPN. Приложение «Настройки» предложит пользователю устройства продолжить или отменить действие.

Поскольку система (а не человек) запускает и останавливает постоянное соединение, вам необходимо адаптировать поведение и пользовательский интерфейс вашего приложения:

  1. Отключите любой пользовательский интерфейс, который отключает соединение, поскольку система и приложение «Настройки» контролируют соединение.
  2. Сохраняйте любую конфигурацию между каждым запуском приложения и настраивайте соединение с последними настройками. Поскольку система запускает ваше приложение по требованию, человек, использующий устройство, не всегда может захотеть настраивать соединение.

Вы также можете использовать управляемые конфигурации для настройки соединения. Управляемые конфигурации помогают ИТ-администратору удаленно настроить VPN.

Обнаружение постоянного включения

В Android нет API-интерфейсов для проверки того, запустила ли система вашу службу VPN. Но когда ваше приложение помечает любые запущенные экземпляры служб, вы можете предположить, что система запустила неотмеченные службы для постоянно включенного VPN. Вот пример:

  1. Создайте экземпляр Intent для запуска службы VPN.
  2. Пометьте службу VPN, добавив в намерение дополнительное значение .
  3. В методе 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-соединение. Приложение должно быть установлено на устройстве, когда вы добавляете его в список.

Котлин

// 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()

Ява

// 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.

Запрещенные приложения

Чтобы добавить приложение в список запрещенных, вызовите VpnService.Builder.addDisallowedApplication() . Запрещенные приложения используют системную сеть, как если бы VPN не был запущен — все остальные приложения используют VPN.

Обход VPN

Ваш VPN может позволить приложениям обходить VPN и выбирать собственную сеть. Чтобы обойти VPN, вызовите VpnService.Builder.allowBypass() при установке VPN-интерфейса. Вы не можете изменить это значение после запуска службы VPN. Если приложение не привязывает свой процесс или сокет к определенной сети, сетевой трафик приложения продолжается через VPN.

Приложения, которые привязаны к определенной сети, не имеют соединения, когда кто-то блокирует трафик, который не проходит через VPN. Чтобы отправить трафик через определенную сеть, приложения перед подключением сокета вызывают методы, такие как ConnectivityManager.bindProcessToNetwork() или Network.bindSocket() .

Пример кода

Проект Android с открытым исходным кодом включает пример приложения под названием ToyVPN . Это приложение показывает, как настроить и подключить службу VPN.