Wykrywanie aktywnego VPN w aplikacji Mac w sandboxie
Nie ma publicznego API odpowiadającego na to pytanie, a sztuczka z nazwą interfejsu to tylko heurystyka. Sześć kontroli, które pomogą zdecydować, na czym Twoja aplikacja naprawdę powinna polegać.
Potrzebujesz jednego ekranu, który zachowuje się inaczej, gdy tunel jest aktywny, a nie ma wywołania, które by ci to powiedziało. To ograniczenie wydawnicze: w macOS żadna publiczna struktura nie informuje, czy dowolny VPN jest połączony, a w iOS stan należy do aplikacji, która zainstalowała konfigurację. Poniższe kontrole prowadzą od tego ograniczenia do czegoś, co możesz wydać, i każda z nich może wcześniej zakończyć poszukiwania.
Kontrola 1: czy na to pytanie w ogóle da się odpowiedzieć?
Sandbox rozstrzyga pytanie, zanim zrobi to jakiekolwiek API. Proces strony trzeciej nie może wyliczyć rozszerzenia innego dostawcy ani odczytać konfiguracji tego rozszerzenia, więc nie ma żadnego obsługiwanego obiektu, o który można zapytać.
Każde obejście trafia na tę samą ścianę. Listy interfejsów, scutil --nc list i bezpośrednie odczyty konfiguracji systemu albo wymagają uprawnień, których sandbox odmawia, albo zależą od zachowania, którego Apple nigdy nie obiecał. Jeśli plan potrzebuje któregoś z nich, problemem jest plan. Funkcja zbudowana na nieudokumentowanym szczególe to zgłoszenie do pomocy technicznej z datą dostawy.
Kontrola 2: czy twoja aplikacja sama zarządza tunelem?
Jeśli tak, masz obsługiwaną odpowiedź dla własnej konfiguracji. Wczytaj preferencje, obsłuż błąd, następnie odczytaj stan i subskrybuj zmiany zamiast odpytywać:
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
}Bądź precyzyjny co do tego, co to zwraca: stan tunelu, który zainstalowałeś. Użytkownik korzystający z innego klienta pojawi się jako rozłączony i to nie jest błąd w twoim kodzie. Zdecyduj, które z tych dwóch pytań naprawdę zadaje twoja funkcja, zanim zbudujesz na odpowiedzi.
Kontrola 3: co heurystyka naprawdę by ci powiedziała?
Szeroko kopiowane podejście pyta słownik serwera proxy systemu o jego wpisy z zakresem i odczytuje klucze, które przypadkiem są nazwami interfejsów:
let settings = CFNetworkCopySystemProxySettings()?.takeRetainedValue() as NSDictionary?
let scoped = settings?["__SCOPED__"] as? [String: Any]
let names = scoped?.keys ?? []Klucz zaczynający się od tap, tun, ppp, ipsec lub utun oznacza, że istnieje interfejs objęty zakresem tunelu. Dopasowanie całej nazwy zawodzi od razu, ponieważ iOS 12 przyszedł z utun1 i utun2, a niektóre profile pojawiają się jako ipsec4, ppp0 lub utun0. Porównuj raczej prefiksy.
Mimo to nazwa nie identyfikuje żadnego właściciela. Funkcje systemowe i rozszerzenia innych dostawców również tworzą interfejsy utun, a jeden może istnieć, nie przenosząc nic z twojego ruchu. Czytaj to jako wskazówkę, że w pobliżu jest coś w kształcie tunelu, nic więcej. Fałszywy alarm jest tu tani, jeśli ograniczasz tylko jedną funkcję, i drogi, jeśli ograniczasz ekran logowania.
Kontrola 4: czy możesz zamiast tego przetestować cel?
Jeśli aplikacja musi tylko wiedzieć, czy wewnętrzna usługa jest osiągalna, przetestuj tę usługę. Krótka próba z limitem czasu odpowiada na pytanie, od którego funkcja naprawdę zależy, i działa dalej, gdy użytkownik zmieni klienta lub tunel zmieni kształt.
Osiągalność to obserwacja o twojej ścieżce. Nazwa interfejsu to wnioskowanie o czymś sąsiadującym z twoją ścieżką, dlatego heurystyka tworzy spory w raportach błędów, a próba tworzy dowody. Jeśli próba zawiedzie, gdy użytkownik upiera się, że VPN jest włączony, masz teraz konkretną ścieżkę do debugowania: rozwiązywanie nazw, trasę lub uprawnienie usługi.
Kontrola 5: czy platforma może wymusić to, czego potrzebujesz?
Gdy wymaganiem jest gwarancja, że nic nie wydostanie się z tunelu, profil jest właściwym miejscem, a nie detektor. Włączenie includeAllNetworks w konfiguracji pełnego tunelu w iOS 14 lub nowszym zmienia zachowanie platformy zamiast kazać aplikacji to wywnioskować. Flaga jest udokumentowana w dokumentacji NEVPNProtocol.
To inny rodzaj odpowiedzi: egzekwowanie zamiast obserwacji. Dotyczy też tylko profili, które kontrolujesz, więc nie pomaga aplikacji konsumenckiej obserwującej cudzy tunel.
Kontrola 6: spraw, by awaria była bezpieczna i obserwowalna
Wyłącz tylko tę możliwość, która wymaga tunelu, i pozostaw resztę aplikacji użyteczną. Detektor, który za każdym razem, gdy nie jest pewny, ukrywa cały interfejs, tworzy awarię dla każdego, czyj tunel używa nazwy, której nigdy nie przewidziałeś. Wróć raczej do konserwatywnego zachowania: funkcje lokalne działają dalej i wyłączane jest tylko to zdalne wywołanie, które naprawdę potrzebuje tunelu. Ten wybór jest decyzją produktową i zasługuje na zapisanie obok heurystyki.
Zarejestruj, co zaobserwowałeś: nazwy interfejsów, wersję platformy i wynik jednej próby. To zamienia następny raport ze sporu w kontrolę. Potem preferuj obsługiwaną deklarację, gdy ją masz, ponieważ zarządzana konfiguracja może wprost nieść intencję i usunąć większość powodu do sprawdzania nazw.
Jeśli kontrole od 1 do 5 zostawiły cię bez obsługiwanej odpowiedzi, wydaj próbę i uczciwie oznacz wynik. Udokumentowany sygnał najlepszych starań to lepsza decyzja produktowa niż prywatne API, które psuje się w następnym wydaniu.