Виявлення активного VPN у програмі Mac у пісочниці
Публічного API для цього питання немає, а трюк з іменем інтерфейсу — лише евристика. Шість перевірок, щоб вирішити, на що ваш застосунок справді має покладатися.
Потрібен один екран, який поводиться інакше, коли тунель активний, а виклику, який повідомить вам про це, немає. Це обмеження випуску: у macOS жоден публічний фреймворк не повідомляє, чи підключено довільний VPN, а в iOS стан належить програмі, яка встановила конфігурацію. Перевірки нижче ведуть від цієї межі до того, що можна випустити, і кожна з них може припинити пошук раніше.
Перевірка 1: чи можна взагалі відповісти на це питання?
Пісочниця вирішує питання раніше за будь-який API. Процес третьої сторони не може перелічити розширення іншого постачальника і не може прочитати конфігурацію цього розширення, тому немає підтримуваного об'єкта, який можна запитати.
Кожен обхід упирається в ту саму стіну. Списки інтерфейсів, scutil --nc list і пряме читання системної конфігурації або вимагають прав, яких пісочниця не дає, або залежать від поведінки, яку Apple ніколи не обіцяла. Якщо плану потрібне щось із цього, проблема в плані. Функція, побудована на недокументованій деталі, — це тікет у підтримку з датою постачання.
Перевірка 2: програма сама керує тунелем?
Якщо так, у вас є підтримувана відповідь для власної конфігурації. Завантажте налаштування, обробіть помилку, потім прочитайте стан і підпишіться на зміни замість опитування:
vpnManager.loadFromPreferences { error in
// handle a load failure before reading status
}
NotificationCenter.default.addObserver(
forName: .NEVPNStatusDidChange,
object: nil,
queue: nil
) { note in
let status = (note.object as? NEVPNConnection)?.status
}Будьте точні щодо того, що це повертає: стан тунелю, який ви встановили. Користувач з іншим клієнтом відображатиметься як від'єднаний, і це не помилка у вашому коді. Вирішіть, яке з цих двох питань насправді ставить ваша функція, перш ніж будувати на відповіді.
Перевірка 3: що евристика насправді вам скаже?
Широко скопійований підхід запитує в системного словника проксі його записи з областю дії та читає ключі, які випадково є іменами інтерфейсів:
let settings = CFNetworkCopySystemProxySettings()?.takeRetainedValue() as NSDictionary?
let scoped = settings?["__SCOPED__"] as? [String: Any]
let names = scoped?.keys ?? []Ключ, що починається з tap, tun, ppp, ipsec або utun, означає, що присутній інтерфейс з областю дії тунелю. Зіставлення повного імені одразу провалюється, бо iOS 12 постачалася з utun1 і utun2, а деякі профілі виглядають як ipsec4, ppp0 або utun0. Порівнюйте префікси.
Тим не менш ім'я не визначає власника. Системні функції та розширення інших постачальників теж створюють інтерфейси utun, і такий може існувати, не несучи нічого з вашого трафіку. Читайте це як підказку, що поруч є щось у формі тунелю, і не більше. Хибне спрацювання тут дешеве, якщо ви обмежуєте одну функцію, і дороге, якщо обмежуєте екран входу.
Перевірка 4: чи можна натомість перевірити пункт призначення?
Якщо програмі потрібно лише знати, чи доступна внутрішня служба, перевірте цю службу. Коротка проба з тайм-аутом відповідає на питання, від якого функція справді залежить, і продовжує працювати, коли користувач змінює клієнт або тунель змінює форму.
Доступність — це спостереження про ваш шлях. Ім'я інтерфейсу — це висновок про щось суміжне з вашим шляхом, тому евристика породжує суперечки у звітах про помилки, а проба породжує докази. Якщо проба не проходить, поки користувач наполягає, що VPN увімкнено, у вас тепер є конкретний шлях для налагодження: розв'язання імен, маршрут або дозвіл служби.
Перевірка 5: чи може платформа примусово забезпечити потрібне?
Коли вимога — гарантія, що ніщо не вирветься з тунелю, профіль і є місцем для цього, а не детектор. Увімкнення includeAllNetworks у конфігурації повного тунелю на iOS 14 або новішій змінює поведінку платформи замість того, щоб просити програму це вивести. Прапорець задокументовано в довідці NEVPNProtocol.
Це інший вид відповіді: примус замість спостереження. Він до того ж застосовний лише до профілів, які ви контролюєте, тому не допомагає споживчій програмі, що спостерігає за чужим тунелем.
Перевірка 6: зробіть відмову безпечною та спостережуваною
Вимикайте лише ту можливість, якій потрібен тунель, і залишайте решту програми працездатною. Детектор, який щоразу, коли не впевнений, ховає весь інтерфейс, створює збій для кожного, чий тунель використовує ім'я, якого ви ніколи не передбачали. Поверніться натомість до консервативної поведінки: локальні функції продовжують працювати, і вимикається лише той віддалений виклик, якому справді потрібен тунель. Цей вибір — продуктове рішення, і він заслуговує бути записаним поруч з евристикою.
Записуйте те, що спостерігали: імена інтерфейсів, версію платформи та результат однієї проби. Це перетворює наступний звіт зі суперечки на перевірку. Потім віддавайте перевагу підтримуваній декларації, коли вона у вас є, бо керована конфігурація може явно нести намір і прибрати більшу частину причини взагалі перевіряти імена.
Якщо перевірки з 1 по 5 залишили вас без підтримуваної відповіді, випустіть пробу і чесно позначте результат. Задокументований сигнал найкращого зусилля — краще продуктове рішення, ніж приватний API, який ламається в наступному випуску.