Когда вы строите экран VPN внутри приложения iOS, первый сюрприз в том, что список протоколов в приложении «Настройки» — не список API. Фреймворк Network Extension позволяет приложению создать одну личную конфигурацию VPN, принадлежащую этому приложению, и принимает лишь несколько объектов протокола. Всё остальное, чего люди ждут, от L2TP до загруженного профиля, находится вне этой поверхности или является приватным для Apple. Ошибки, которые следуют, обычно не баги: это API отказывается от работы, которую ему никогда не давали.

Миф: API — это редактор «Настроек»

Считается, что раз iOS умеет настраивать профили IKEv1, IKEv2, L2TP и IPsec, приложение может создать любой из них программно. Не может. Фреймворк предоставляет вызывающему приложению ровно один слот конфигурации, и этот слот принимает лишь небольшой набор публичных классов протоколов. Всё остальное, чего люди ждут, от L2TP до загруженного профиля, находится вне этой поверхности или является приватным для Apple.

Ошибки, которые следуют, обычно не баги. Это API отказывается от работы, которую ему никогда не давали, а решение — проектировать экран вокруг того, чем владеет API.

Три объекта протокола и те, которых нет

Ваши варианты — IKEv1 через NEVPNProtocolIPSec, IKEv2 через NEVPNProtocolIKEv2 и NETunnelProviderProtocol, если вы поставляете собственное расширение пакетного туннеля. Справочник NEVPNManager перечисляет допустимые объекты протокола. L2TP и PPTP никогда не появляются в этом списке, потому что Apple не опубликовала для них классы. Попытка любого из них заканчивается при сохранении ошибкой или, что хуже, конфигурацией, которую система молча отказывается использовать.

Код для настольных систем следует другим правилам. В macOS SCNetworkInterfaceCreateWithInterface укладывает тип интерфейса, например L2TP поверх IPv4, и прикрепляет его к сетевой службе, а networksetup -connectpppoeservice запускает результат. Нужны права администратора и вспомогательный инструмент, поскольку общий секрет и пароль попадают в системную связку ключей. Скопируйте этот дизайн на iOS — и ничего не скомпилируется.

Передавайте ссылку на связку ключей, а не пароль

Ни один из двух классов IKE не принимает пароль как текст. Два свойства passwordReference и sharedSecretReference хотят постоянную ссылку на связку ключей, которую вы получаете из SecItemCopyMatching, запросив kSecReturnPersistentRef. Передача данных элемента или вставка строки в поле даёт сбой сохранения или соединение с пустыми учётными данными. Ссылка долговечна ровно настолько, насколько долговечен элемент связки ключей за ней, поэтому элемент должен существовать до сборки конфигурации. Создайте его в той же транзакции, что и сохранение, и вы уберёте целый класс ошибок порядка.

Аутентификация по сертификату идёт другим путём: identityData плюс identityDataPassword для блоба PKCS#12. Поставка этого блоба внутри пакета приложения — привычка, перенесённая из примера кода, а не решение о выпуске, потому что пароль путешествует вместе с ним и каждая копия приложения несёт одну и ту же идентичность.

Право и доступность закрывают всё

Менеджера может не быть вовсе, и причина — провижининг. Идентификатору вашего приложения нужна возможность Network Extension вместе с правом на личный VPN, и профиль, подписывающий сборку, должен нести оба.

Пропустите это, и общий экземпляр вернёт nil, либо сохранение не удастся с ошибкой, которая никогда не упоминает право. Доступность — другие ворота. Если вы нацелены на что-то старше iOS 8, символа нет во время выполнения, поэтому сбой — это падение или nil-менеджер, а не менеджер без конфигурации. Проверьте среду выполнения и право, прежде чем отлаживать граф объектов.

Загрузите, измените, сохраните, затем следите за порядком по запросу

Относитесь к жизненному циклу как к загрузке, изменению и сохранению и никогда не пропускайте загрузку. Пока loadFromPreferences не завершится, менеджер ничего не держит, и объект протокола нельзя прочитать или заменить.

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

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

Похожие статьи