Perché la tua app VPN iOS mostra disconnesso mentre il tunnel è attivo
La tua app iOS controlla il tunnel ma non lo possiede, e lo stato del gestore è in cache. Carica prima le preferenze, poi leggi stato e contatori dell’interfaccia.
La tua app dice disconnesso, la barra dei menu dice connesso, e il traffico scorre chiaramente. Quel divario non è una race condition che risolvi con un nuovo tentativo: l’app e il tunnel sono processi separati, e l’oggetto che il tuo codice legge è una vista in cache di una configurazione che il sistema conserva. La superficie Personal VPN di Apple è un’API di controllo, non un percorso dati, e gran parte del suo comportamento strano deriva da questo unico fatto. Ecco cosa succede davvero a ogni chiamata.
L’oggetto gestore è una vista in cache, non il tunnel
NETunnelProviderManager viene popolato da un archivio di preferenze di proprietà del sistema, quindi le sue proprietà non significano nulla finché loadFromPreferences non è terminato. Se leggi connection.status prima, ottieni un valore predefinito, ed è per questo che il primo aggiornamento di stato dopo un riavvio riporta disconnesso mentre il tunnel è attivo. Carica prima, poi leggi stato e configurazione del protocollo dallo stesso gestore di completamento. Osserva i cambiamenti successivi tramite NEVPNStatusDidChange invece di interrogare, e nota che la notifica scatta anche per le connessioni avviate da On Demand, dove non esiste alcuna chiamata dell’app che possa restituire qualcosa. Lo stesso vale per le impostazioni del protocollo: si leggono dalla configurazione caricata, non da ciò che l’app ha passato.
Il tunnel stesso vive in un processo provider di Network Extension, quindi l’app può perdere il proprio stato senza perdere la connessione. Tutto ciò che l’interfaccia mostra va ricostruito dal gestore, non ricordato dalla sessione precedente.
Ordine di avvio: salva, carica, poi avvia
Una connessione basata su provider richiede tre passaggi in ordine. Scrivi la configurazione del provider e chiama saveToPreferences, carica il gestore perché la configurazione salvata sia quella che hai in mano, poi avvia tramite NETunnelProviderSession.startVPNTunnel(). Se salti il salvataggio, l’avvio non ha nulla da lanciare; se carichi prima del salvataggio, il gestore riflette ancora la vecchia configurazione.
Il booleano restituito da startVPNTunnel dice solo che la richiesta è stata accettata. I guasti a livello di provider arrivano più tardi come cambio di stato in disconnesso, che osservi tramite la notifica NEVPNStatusDidChange, e il gestore della notifica deve rileggere connection.status dopo loadFromPreferences perché il valore in cache può essere ancora quello vecchio. I guasti a livello di richiesta tornano subito dalla forma che lancia eccezioni della chiamata, o tramite il parametro di uscita dell’errore di startVPNTunnelAndReturnError:. Il codice che tratta il valore di ritorno come esito segnala una connessione che non si è mai stabilita. L’altro guasto silenzioso è un identificatore di bundle del provider che non corrisponde all’estensione installata: la richiesta viene rifiutata, e nulla nel messaggio punta all’identificatore. Se l’identificatore è sbagliato, la richiesta di solito fallisce subito, prima che compaia qualsiasi riga di log del provider.
Ricaricare le preferenze dopo un avvio è ciò che rende corretti gli schermi di stato. Il gestore non ti spinge una nuova configurazione quando il sistema la cambia.
Il materiale di fiducia arriva tramite profilo, mai dal codice dell’app
Un’app non ha alcun modo supportato di aggiungere un certificato radice all’archivio di fiducia iOS, quindi una configurazione Personal VPN può referenziare solo materiale di cui il dispositivo si fida già: una catena stabilita da un profilo installato, dalla gestione del dispositivo o da una CA di cui il sistema si fida per altri motivi. Un’app che crea la propria CA al primo avvio e si aspetta che il sistema la onori fallirà la validazione dentro il provider, dove l’interfaccia dell’app non può vedere il motivo.
Tratta quel confine come il vincolo di progettazione che è e consegna l’ancora di fiducia insieme al profilo di iscrizione. Tieni l’autenticazione VPN separata dalla decisione di fiducia, perché falliscono in punti diversi e le debuggano persone diverse.
I contatori vengono dall’interfaccia, non dall’API VPN
Il gestore espone stato e configurazione, e nulla che conti byte per tunnel. Se ti servono cifre d’uso, chiama getifaddrs() e leggi if_data dell’interfaccia pertinente: en0 per il Wi-Fi, le interfacce pdp_ip per il cellulare e un dispositivo utun per il tunnel stesso. I contatori di utun descrivono il traffico interno e includono l’overhead del protocollo.
Queste cifre sono stime, non contabilità per app, e si azzerano quando l’interfaccia viene ricreata. Etichettale così nell’interfaccia, o qualcuno le userà per fatturare un cliente. Se ti servono cifre per app, ti serve un meccanismo del tutto diverso, non una lettura migliore di questi campi.
La pulizia sta in stopVPNTunnel, non nella terminazione
applicationWillTerminate ti dà qualche secondo prima che il sistema possa uccidere il processo, e non è un posto affidabile per smontare una connessione. Invia lo stop a stopVPNTunnel() o gestiscilo nel callback di stop del provider, e conserva l’identificatore del gestore o della configurazione con cui hai avviato, così lo stop colpisce quella connessione invece di ogni configurazione sul dispositivo.
Memorizza quell’identificatore accanto al profilo a cui appartiene, poiché un oggetto gestore non registra quale configurazione ha scelto l’utente. E ricorda che una connessione sollevata da On Demand non ha alcuna chiamata dell’app dietro, quindi uno schermo di stato deve comunque caricare le preferenze prima di disegnare qualcosa.