Ti serve una schermata che si comporti diversamente quando il tunnel è attivo, e non c'è nessuna chiamata che te lo dica. È il vincolo di spedizione: su macOS nessun framework pubblico riporta se una VPN arbitraria è connessa, e su iOS lo stato appartiene all'app che ha installato la configurazione. I controlli qui sotto vanno da quel limite a qualcosa che puoi spedire, e ciascuno di essi può chiudere la ricerca in anticipo.

Controllo 1: la domanda ha una risposta, almeno?

La sandbox risolve la domanda prima di qualsiasi API. Un processo di terze parti non può enumerare l'estensione di un altro fornitore, né può leggere la configurazione di quell'estensione, quindi non c'è nessun oggetto supportato a cui chiedere.

Ogni aggiramento sbatte contro lo stesso muro. Gli elenchi delle interfacce, scutil --nc list e le letture dirette della configurazione di sistema o richiedono diritti che la sandbox trattiene o dipendono da un comportamento che Apple non ha mai promesso. Se il piano ha bisogno di una di queste cose, il problema è il piano. Una funzionalità costruita su un dettaglio non documentato è un ticket di supporto con una data di consegna.

Controllo 2: la tua app gestisce il tunnel da sola?

Se lo fa, hai una risposta supportata per la tua configurazione. Carica le preferenze, gestisci l'errore, poi leggi lo stato e iscriviti ai cambiamenti invece di fare polling:

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
}

Sii preciso su cosa restituisce questo: lo stato del tunnel che hai installato. Un utente che esegue un altro client apparirà come disconnesso, e non è un bug nel tuo codice. Decidi quale di queste due domande la tua funzionalità sta davvero ponendo prima di costruire sulla risposta.

Controllo 3: cosa ti direbbe davvero un'euristica?

L'approccio largamente copiato chiede al dizionario del proxy di sistema le sue voci con ambito e legge le chiavi, che per caso sono nomi di interfaccia:

let settings = CFNetworkCopySystemProxySettings()?.takeRetainedValue() as NSDictionary?
let scoped = settings?["__SCOPED__"] as? [String: Any]
let names = scoped?.keys ?? []

Una chiave che inizia con tap, tun, ppp, ipsec o utun implica che sia presente un'interfaccia con ambito tunnel. Confrontare il nome intero fallisce subito, perché iOS 12 è arrivato con utun1 e utun2 e alcuni profili compaiono come ipsec4, ppp0 o utun0. Confronta invece i prefissi.

Anche così, un nome non identifica alcun proprietario. Anche le funzionalità di sistema e le estensioni di altri fornitori creano interfacce utun, e una può esistere senza portare nulla del tuo traffico. Leggilo come un indizio che c'è qualcosa a forma di tunnel nelle vicinanze, nulla di più forte. Un falso positivo qui costa poco se condizioni una sola funzionalità, e molto se condizioni la schermata di accesso.

Controllo 4: puoi invece testare la destinazione?

Se l'app deve solo sapere se un servizio interno è raggiungibile, testa quel servizio. Una sonda breve con un timeout risponde alla domanda da cui la funzionalità dipende davvero, e continua a funzionare quando l'utente cambia client o il tunnel cambia forma.

La raggiungibilità è un'osservazione sul tuo percorso. Un nome di interfaccia è un'inferenza su qualcosa adiacente al tuo percorso, ed è per questo che l'euristica produce discussioni nei bug report e la sonda produce prove. Se la sonda fallisce mentre l'utente insiste che la VPN è attiva, ora hai un percorso concreto da debuggare: risoluzione dei nomi, rotta o permesso del servizio.

Controllo 5: la piattaforma può imporre ciò che ti serve?

Quando il requisito è la garanzia che nulla sfugga al tunnel, il profilo è il posto giusto, non il rilevatore. Attivare includeAllNetworks in una configurazione a tunnel completo su iOS 14 o successivo cambia il comportamento della piattaforma invece di chiedere alla tua app di inferirlo. Il flag è documentato nella referenza NEVPNProtocol.

È un altro tipo di risposta: imposizione invece di osservazione. Si applica anche solo ai profili che controlli, quindi non aiuta un'app consumer che osserva il tunnel di qualcun altro.

Controllo 6: rendi il guasto sicuro e osservabile

Spegni solo la capacità che ha bisogno del tunnel e lascia utilizzabile il resto dell'app. Un rilevatore che nasconde l'intera interfaccia ogni volta che è incerto fabbrica un'interruzione per chiunque abbia un tunnel con un nome che non hai mai previsto. Torna invece al comportamento conservativo: le funzionalità locali continuano a funzionare e solo la chiamata remota che ha davvero bisogno del tunnel viene disattivata. Questa scelta è una decisione di prodotto, e merita di essere scritta accanto all'euristica.

Registra ciò che hai osservato: nomi delle interfacce, versione della piattaforma e risultato di una sonda. Questo trasforma il prossimo report da una discussione in un controllo. Poi preferisci una dichiarazione supportata quando ne hai una, perché una configurazione gestita può portare l'intento in modo esplicito e togliere gran parte del motivo di ispezionare i nomi.

Se i controlli da 1 a 5 ti hanno lasciato senza una risposta supportata, spedisci la sonda ed etichetta il risultato con onestà. Un segnale documentato di massimo sforzo è una decisione di prodotto migliore di un'API privata che si rompe alla prossima versione.

Articoli correlati