Реализация VPN-подключения из мобильного приложения (Per-App VPN)
Корпоративное приложение обращается к внутреннему API через публичный интернет, потому что разворачивать полноценный device-level VPN на BYOD-устройствах сотрудников ИТ-отдел не готов. Нужен туннель только для трафика конкретного приложения — остальное идёт напрямую. Это и есть Per-App VPN.
Две принципиально разные архитектуры
На Android Per-App VPN строится на VpnService из пакета android.net.VpnService. Приложение создаёт виртуальный TUN-интерфейс и само обрабатывает IP-пакеты — либо форвардит их в корпоративный шлюз через WireGuard/OpenVPN, либо использует HTTP CONNECT proxy. Ограничить туннель конкретным приложением можно через VpnService.Builder.addAllowedApplication():
val builder = VpnService.Builder()
.addAddress("10.0.0.2", 32)
.addRoute("192.168.1.0", 24) // только корпоративная подсеть
.addAllowedApplication("com.company.app") // только наш пакет
.setSession("Corp VPN")
.setMtu(1400)
val vpnInterface = builder.establish()
Если вместо addAllowedApplication использовать addDisallowedApplication — туннель работает для всех приложений кроме указанных. Нужно точно понимать, какой сценарий требует заказчик.
На iOS всё иначе. Apple не даёт прямого доступа к TUN-интерфейсу из обычного приложения. Per-App VPN реализуется через Network Extension framework — конкретно NEAppProxyProvider (для proxy-based) или NETunnelProvider (для VPN-туннеля). Оба варианта требуют специального entitlement com.apple.developer.networking.networkextension, который выдаётся через Apple Developer Portal и стоит дополнительного ревью.
// Конфигурация через NEVPNManager
let manager = NEVPNManager.shared()
manager.loadFromPreferences { error in
let proto = NEVPNProtocolIKEv2()
proto.serverAddress = "vpn.corp.example.com"
proto.authenticationMethod = .certificate
proto.identityReference = certRef // из Keychain
proto.useExtendedAuthentication = false
manager.protocolConfiguration = proto
manager.isEnabled = true
manager.saveToPreferences { _ in
try? NEVPNManager.shared().connection.startVPNTunnel()
}
}
Разница в том, что NEVPNManager — это device-level VPN, управляемый через системные настройки. Для настоящего per-app на iOS нужен MDM профиль с PerAppVPN конфигурацией. Без MDM нельзя ограничить туннель одним приложением средствами iOS.
Где ломается чаще всего
Android: Always-On VPN и Doze Mode. Если требуется постоянное соединение, VpnService нужно объявить как foreground service. В Doze Mode система убивает фоновые сервисы, и туннель рвётся без предупреждения. Решение — PowerManager.WakeLock + JobScheduler для реконнекта, либо переход на WireGuard-based решение, которое лучше переживает засыпание.
iOS: смерть расширения. NEAppProxyProvider работает в отдельном Extension process с ограниченным временем жизни. Если extension крашится — iOS не всегда его перезапускает немедленно. Crashlytics в extension не работает по умолчанию (нет main bundle), нужно инициализировать SDK вручную с явным путём к plist.
Обход корпоративного proxy на Android 10+. Начиная с API 29, приложения в режиме PRIVATE_DNS по умолчанию не используют системный proxy. Если корпоративная сеть маршрутизирует через HTTP proxy — нужно явно прописать Proxy.setDefaultSelector() или использовать ProxySelector в OkHttp:
val client = OkHttpClient.Builder()
.proxySelector(CorpProxySelector(proxyHost, proxyPort))
.build()
Что реально нужно выяснить до начала разработки
- WireGuard, OpenVPN или IKEv2 на стороне шлюза?
- BYOD или корпоративные устройства с MDM?
- Нужен ли split-tunneling (только корпоративные хосты через VPN)?
- Требования к certificate pinning на корпоративном шлюзе?
Ответы на эти вопросы меняют архитектуру кардинально.
Этапы и сроки
Проектирование + выбор протокола → разработка VpnService/Network Extension → интеграция с корпоративным шлюзом → тестирование на реальных девайсах (включая режим авиа и Doze).
На Android с WireGuard-туннелем через готовую библиотеку wireguard-android — 3–4 дня. Кастомный VpnService с проксированием — 5–7 дней. iOS с NETunnelProvider и MDM-профилем — от 1 недели с учётом времени на получение entitlement от Apple.







