API VPN для iOS: NEVPNManager, постачальник тунелю чи профіль?
Виберіть API за транспортом, з яким треба говорити, потім доведіть, що тунель несе трафік: порядок save-load-start, entitlement-и provider та правила on-demand.
На назву iOS VPN відповідають три різні продукти, і вони не поділяють один API. Personal VPN використовує вбудовані в систему транспорти IPsec та IKEv2. Custom tunnel запускає ваш власний протокол усередині network extension. Configuration profile налаштовує будь-який із них і може додати поведінку on-demand. Вибір за транспортом, з яким треба говорити, швидший за вибір за тим посібником, який скомпілювався першим.
Виконуйте перевірки нижче по порядку, бо кожен крок припускає, що попередній дав коректну конфігурацію.
Крок 1: вибирайте за транспортом
Якщо сервер — IKEv2 або IPsec у стилі Cisco, NEVPNManager достатньо, і системні транспорти дістаються безкоштовно. Якщо сервер говорить OpenVPN, WireGuard чи щось інше, NEVPNManager не допоможе: у нього немає гачка для стороннього протоколу. Цей випадок потребує NETunnelProviderManager з packet tunnel provider, який реалізує протокол. Профіль лише розповсюджує конфігурацію і не може додати транспорт, якого немає в операційній системі.
PPTP не є ціллю в поточних системах. IPsec із спільним секретом досі очікує назву групи в remoteIdentifier, елемент keychain із секретом і розширену автентифікацію, коли сервер запитує ім'я користувача. Кожна відсутня частина проявляється як та сама загальна помилка конфігурації, тож додайте їх усі, перш ніж налагоджувати щось інше.
Крок 2: дотримуйтеся порядку збереження, завантаження та запуску
NEVPNManager пише в preferences, читає їх назад і лише потім запускає. Заповніть об'єкти протоколу та manager, викличте saveToPreferences, викличте loadFromPreferences і викличте startVPNTunnel з load completion handler. Запуск до повернення завантаження дає помилку configuration-invalid, NEVPNErrorDomain code 1, на конфігурації, яка в іншому правильна.
Форма достатньо коротка, щоб тримати її в одному місці:
manager.protocolConfiguration = proto
manager.isEnabled = true
manager.saveToPreferences { }
manager.loadFromPreferences { manager.connection.startVPNTunnel() }startVPNTunnel повертається, коли запит прийнято, а не коли тунель піднято. Спостерігайте NEVPNStatusDidChange і читайте connection.status для справжнього стану. Застосунок, який припускає успіх у місці виклику, повідомляє про підключений VPN, поки extension ще веде переговори. Стан connecting нормальний на мить; стан, з якого він ніколи не виходить, — це збій, вартий журналювання.
Крок 3: дайте процесу provider те, що йому потрібно
Custom transport працює в окремому процесі з власним bundle identifier і власним entitlement. І застосунку container, і provider під час підпису потрібен entitlement network-extension, а збірка Developer ID поза App Store все одно має нести його в provisioning profile. Відсутній entitlement виглядає як помилка протоколу, бо extension просто не запускається.
Немає публічного socket descriptor, який можна позичити. NEPacketTunnelProvider дає вам NEPacketTunnelFlow з read, write і cancel, а код, що тягнеться до шляхів приватних ключів, ламається між випусками. Provider продовжує працювати, поки застосунок у фоні, тож застосунку container для цього не потрібен фоновий режим.
Крок 4: on-demand — це набір правил, а не перемикач
isOnDemandEnabled лише зводить правила; NEOnDemandRuleConnect та його побратими вирішують, коли піднімається тунель. Правило evaluate-connection з probe URL варте рівно стільки, скільки вартий цей URL. Якщо probe відповідає без тунелю, правило доходить висновку, що приватна мережа вже доступна, і ніколи не підключається, тож націльте його на те, що відповідає лише зсередини.
Always-on — окрема обіцянка. Йому потрібен контрольований пристрій і керований профіль, а неконтрольований користувач може видалити профіль коли завгодно. Не випускайте дизайн, що залежить від always-on, якщо парк не під контролем.
Крок 5: доведіть шлях, а не ярлик
Сканування інтерфейсів tun, tap, ppp або ipsec доводить, що VPN налаштовано, а не що ваш запит ним скористався. NWPathMonitor з usesInterfaceType(.other) каже, що віртуальний інтерфейс доступний, а viability handler може перевірити один хост. Жодне не доводить шлях виходу, тож поєднайте їх із запитом до ресурсу, до якого дістанеться лише тунель. Назви інтерфейсів — діагностична підказка, а не ворота, на яких має розгалужуватися логіка застосунку.
Для польової перевірки порівняйте публічну адресу з тунелем і без нього та отримайте сторінку лише для внутрішнього доступу. Виклик status може повідомляти про підключення, поки provider веде переговори, тож хай вирішує результат ресурсу, а status вважайте допоміжним доказом.