Din app säger frånkopplad, menyraden säger ansluten, och trafiken flödar tydligt. Gapet är inte ett kapplöpningstillstånd som du lagar med ett nytt försök: appen och tunneln är separata processer, och objektet som din kod läser är en cachad vy av en konfiguration som systemet lagrar. Apples Personal VPN-yta är ett kontroll-API, inte en dataväg, och det mesta av dess märkliga beteende följer av just det. Här är vad som faktiskt händer vid varje anrop.

Hanterarobjektet är en cachad vy, inte tunneln

NETunnelProviderManager fylls från ett inställningslager som systemet äger, så dess egenskaper betyder ingenting förrän loadFromPreferences är klar. Läser du connection.status före det får du ett standardvärde, och därför rapporterar den första statusuppdateringen efter omstart frånkopplad medan tunneln kör. Läs in först och läs sedan status och protokollkonfiguration från samma slutförandehanterare. Följ senare ändringar via NEVPNStatusDidChange i stället för att polla, och notera att aviseringen även utlöses för anslutningar som On Demand startar, där det inte finns något appanrop som kan returnera något. Samma sak gäller protokollinställningar: de läses från den inlästa konfigurationen, inte från vad appen skickade in.

Själva tunneln lever i en Network Extension-leverantörsprocess, så appen kan förlora sitt eget tillstånd utan att förlora anslutningen. Allt gränssnittet visar måste byggas upp igen från hanteraren, inte kommas ihåg från förra sessionen.

Startordning: spara, läs in, starta sedan

En leverantörsbaserad anslutning kräver tre steg i ordning. Skriv leverantörskonfigurationen och anropa saveToPreferences, läs in hanteraren så att den sparade konfigurationen är den du håller i, och starta sedan via NETunnelProviderSession.startVPNTunnel(). Hoppar du över sparandet har starten inget att starta; läser du in före sparandet speglar hanteraren fortfarande den gamla konfigurationen.

Det booleska värdet som startVPNTunnel returnerar säger bara att begäran accepterades. Fel på leverantörsnivå kommer senare som en statusändring till frånkopplad, som du observerar via aviseringen NEVPNStatusDidChange, och aviseringshanteraren måste läsa om connection.status efter loadFromPreferences eftersom det cachade värdet fortfarande kan vara det gamla. Fel på begärandenivå kommer tillbaka direkt från den kastande formen av anropet, eller via felutparametern för startVPNTunnelAndReturnError:. Kod som behandlar returvärdet som resultatet rapporterar en anslutning som aldrig upprättades. Det andra tysta felet är en leverantörsbundle-identifierare som inte matchar det installerade tillägget: begäran avvisas, och ingenting i meddelandet pekar på identifieraren. Är identifieraren fel misslyckas begäran vanligtvis direkt, innan någon loggrad från leverantören visas.

Att läsa in inställningarna igen efter en start är det som gör statusskärmar korrekta. Hanteraren skjuter inte en ny konfiguration till dig när systemet ändrar den.

Förtroendematerial kommer via profil, aldrig från appkod

En app har inget stödt sätt att lägga till ett rotcertifikat i iOS förtroendelager, så en Personal VPN-konfiguration kan bara referera material som enheten redan litar på: en kedja som upprättats av en installerad profil, av enhetshantering eller av en CA som systemet litar på av andra skäl. En app som skapar sin egen CA vid första starten och förväntar sig att systemet godkänner den kommer att misslyckas med valideringen inne i leverantören, där appens gränssnitt inte kan se orsaken.

Behandla den gränsen som den designbegränsning den är och leverera förtroendeankaret tillsammans med registreringsprofilen. Håll VPN-autentisering åtskild från förtroendebeslutet, eftersom de misslyckas på olika ställen och felsöks av olika personer.

Räknarna kommer från gränssnittet, inte från VPN-API:t

Hanteraren exponerar status och konfiguration, och ingenting som räknar byte per tunnel. Behöver du användningssiffror, anropa getifaddrs() och läs if_data för relevant gränssnitt: en0 för Wi-Fi, pdp_ip-gränssnitten för mobilt och en utun-enhet för själva tunneln. Räknarna för utun beskriver inre trafik och inkluderar protokolloverhead.

Dessa siffror är uppskattningar, inte redovisning per app, och de nollställs när gränssnittet skapas om. Märk dem så i gränssnittet, annars använder någon dem för att fakturera en kund. Behöver du siffror per app behöver du en helt annan mekanism, inte en bättre läsning av dessa fält.

Städning hör i stopVPNTunnel, inte i avslutning

applicationWillTerminate ger dig några sekunder innan systemet kan döda processen, och det är inte en pålitlig plats att riva ner en anslutning. Skicka stoppet till stopVPNTunnel() eller hantera det i leverantörens stopp-callback, och behåll hanterar- eller konfigurationsidentifieraren du startade med, så att stoppet träffar den anslutningen i stället för varje konfiguration på enheten.

Spara den identifieraren bredvid profilen den hör till, eftersom ett hanterarobjekt inte registrerar vilken konfiguration användaren valde. Och kom ihåg att en anslutning som On Demand reser inte har något appanrop bakom sig alls, så en statusskärm måste fortfarande läsa in inställningarna innan den ritar något.

Relaterade artiklar