Чому ваш iOS VPN-застосунок показує відключення, коли тунель працює
Ваш iOS-застосунок керує тунелем, але не володіє ним, а стан менеджера кешується. Спершу завантажте налаштування, потім прочитайте статус і лічильники інтерфейсу.
Ваш застосунок каже «відключено», рядок меню — «підключено», а трафік явно йде. Цей розрив не є гонкою, яку виправляють повторною спробою: застосунок і тунель — окремі процеси, а об'єкт, який читає ваш код, — кешоване подання конфігурації, яку зберігає система. Поверхня Personal VPN від Apple — це API керування, а не шлях даних, і більшість її дивної поведінки випливає саме з цього факту. Ось що насправді відбувається під час кожного виклику.
Об'єкт менеджера — кешоване подання, а не тунель
NETunnelProviderManager заповнюється зі сховища налаштувань, яким володіє система, тому його властивості нічого не означають, доки не завершиться loadFromPreferences. Якщо прочитати connection.status раніше, ви отримаєте типове значення, і саме тому перше оновлення статусу після перезапуску повідомляє про відключення, коли тунель працює. Спершу завантажте, потім прочитайте статус і конфігурацію протоколу з того самого обробника завершення. Спостерігайте за подальшими змінами через NEVPNStatusDidChange замість опитування, і зверніть увагу, що сповіщення спрацьовує й для з'єднань, запущених On Demand, де взагалі немає виклику застосунку, який міг би щось повернути. Те саме стосується налаштувань протоколу: вони читаються із завантаженої конфігурації, а не з того, що передав застосунок.
Сам тунель живе в процесі провайдера Network Extension, тому застосунок може втратити власний стан, не втративши з'єднання. Усе, що показує інтерфейс, потрібно знову зібрати з менеджера, а не згадувати з минулої сесії.
Порядок запуску: зберегти, завантажити, потім запустити
З'єднанню на основі провайдера потрібні три кроки по порядку. Запишіть конфігурацію провайдера і викличте saveToPreferences, завантажте менеджер, щоб збережена конфігурація була тією, яку ви тримаєте, потім запустіть через NETunnelProviderSession.startVPNTunnel(). Якщо пропустити збереження, запуску нема чого запускати; якщо завантажити до збереження, менеджер усе ще відображає стару конфігурацію.
Логічне значення, яке повертає startVPNTunnel, каже лише те, що запит прийнято. Відмови на рівні провайдера надходять пізніше як зміна статусу на «відключено», яку ви спостерігаєте через сповіщення NEVPNStatusDidChange, і обробник сповіщення має повторно прочитати connection.status після loadFromPreferences, бо кешоване значення може бути ще старим. Відмови на рівні запиту повертаються негайно з форми виклику, що кидає виняток, або через вихідний параметр помилки startVPNTunnelAndReturnError:. Код, який вважає повернене значення результатом, повідомляє про з'єднання, яке так і не встановилося. Інша тиха відмова — ідентифікатор пакета провайдера, що не відповідає встановленому розширенню: запит відхиляється, і ніщо в повідомленні не вказує на ідентифікатор. Якщо ідентифікатор неправильний, запит зазвичай не вдається одразу, до появи бодай одного рядка журналу провайдера.
Повторне завантаження налаштувань після запуску — це те, що робить екрани статусу правильними. Менеджер не надсилає вам нову конфігурацію, коли система її змінює.
Матеріал довіри надходить через профіль, ніколи з коду застосунку
Застосунок не має підтримуваного способу додати кореневий сертифікат до сховища довіри iOS, тому конфігурація Personal VPN може посилатися лише на матеріал, якому пристрій уже довіряє: ланцюжок, створений встановленим профілем, керуванням пристроєм або CA, якому система довіряє з інших причин. Застосунок, який під час першого запуску створює власний CA і сподівається, що система його визнає, провалить перевірку всередині провайдера, де інтерфейс застосунку не бачить причини.
Ставтеся до цієї межі як до проєктного обмеження, яким вона є, і постачайте якір довіри разом із профілем реєстрації. Тримайте автентифікацію VPN окремо від рішення про довіру, бо вони відмовляють у різних місцях і налагоджуються різними людьми.
Лічильники надходять з інтерфейсу, а не з API VPN
Менеджер надає статус і конфігурацію і нічого, що рахувало б байти на тунель. Якщо потрібні цифри використання, викличте getifaddrs() і прочитайте if_data для відповідного інтерфейсу: en0 для Wi-Fi, інтерфейси pdp_ip для стільникового зв'язку і пристрій utun для самого тунелю. Лічильники utun описують внутрішній трафік і включають накладні витрати протоколу.
Ці цифри — оцінки, а не облік за застосунками, і вони скидаються, коли інтерфейс створюється заново. Позначайте їх так в інтерфейсі, інакше хтось виставить за ними рахунок клієнтові. Якщо потрібні цифри за застосунками, потрібен зовсім інший механізм, а не краще читання цих полів.
Очищення належить stopVPNTunnel, а не завершенню
applicationWillTerminate дає вам кілька секунд, перш ніж система може вбити процес, і це не надійне місце для розриву з'єднання. Надішліть зупинку в stopVPNTunnel() або обробіть її у зворотному виклику зупинки провайдера, і збережіть ідентифікатор менеджера або конфігурації, з яким ви запускали, щоб зупинка націлювалася на це з'єднання, а не на кожну конфігурацію на пристрої.
Зберігайте цей ідентифікатор поруч із профілем, до якого він належить, оскільки об'єкт менеджера не записує, яку конфігурацію вибрав користувач. І пам'ятайте, що з'єднання, підняте On Demand, взагалі не має за собою виклику застосунку, тому екран статусу все одно має завантажити налаштування, перш ніж щось малювати.