Votre app dit déconnecté, la barre de menus dit connecté, et le trafic coule visiblement. Cet écart n’est pas une condition de course que vous corrigez avec un nouvel essai : l’app et le tunnel sont des processus séparés, et l’objet que lit votre code est une vue en cache d’une configuration que le système stocke. La surface Personal VPN d’Apple est une API de contrôle, pas un chemin de données, et l’essentiel de son comportement étrange découle de ce seul fait. Voici ce qui se passe réellement à chaque appel.

L’objet gestionnaire est une vue en cache, pas le tunnel

NETunnelProviderManager est rempli depuis un magasin de préférences que le système possède, donc ses propriétés ne signifient rien tant que loadFromPreferences n’est pas terminé. Lisez connection.status avant cela et vous obtenez une valeur par défaut, ce qui explique pourquoi la première actualisation d’état après un redémarrage signale déconnecté alors que le tunnel est actif. Chargez d’abord, puis lisez l’état et la configuration du protocole depuis le même gestionnaire d’achèvement. Observez les changements ultérieurs via NEVPNStatusDidChange au lieu d’interroger, et notez que la notification se déclenche aussi pour les connexions lancées par On Demand, où aucun appel d’app n’existe pour retourner quoi que ce soit. Il en va de même pour les réglages du protocole : ils sont lus depuis la configuration chargée, pas depuis ce que l’app a transmis.

Le tunnel lui-même vit dans un processus fournisseur Network Extension, donc l’app peut perdre son propre état sans perdre la connexion. Tout ce que l’interface affiche doit être reconstruit à partir du gestionnaire, pas mémorisé de la dernière session.

Ordre de démarrage : enregistrer, charger, puis démarrer

Une connexion basée sur un fournisseur demande trois étapes dans l’ordre. Écrivez la configuration du fournisseur et appelez saveToPreferences, chargez le gestionnaire pour que la configuration enregistrée soit celle que vous tenez, puis démarrez via NETunnelProviderSession.startVPNTunnel(). Si vous sautez l’enregistrement, le démarrage n’a rien à lancer ; si vous chargez avant l’enregistrement, le gestionnaire reflète encore l’ancienne configuration.

Le booléen renvoyé par startVPNTunnel dit seulement que la requête a été acceptée. Les échecs au niveau du fournisseur arrivent plus tard comme un changement d’état vers déconnecté, que vous observez via la notification NEVPNStatusDidChange, et le gestionnaire de notification doit relire connection.status après loadFromPreferences car la valeur en cache peut encore être l’ancienne. Les échecs au niveau de la requête reviennent immédiatement de la forme levant une exception de l’appel, ou via le paramètre de sortie d’erreur de startVPNTunnelAndReturnError:. Le code qui traite la valeur de retour comme le résultat signale une connexion qui ne s’est jamais établie. L’autre échec silencieux est un identifiant de paquet de fournisseur qui ne correspond pas à l’extension installée : la requête est rejetée, et rien dans le message ne pointe vers l’identifiant. Si l’identifiant est faux, la requête échoue généralement aussitôt, avant qu’aucune ligne de journal du fournisseur n’apparaisse.

Recharger les préférences après un démarrage est ce qui rend les écrans d’état corrects. Le gestionnaire ne vous pousse pas une nouvelle configuration quand le système la modifie.

Le matériel de confiance arrive par profil, jamais depuis le code de l’app

Une app n’a aucun moyen pris en charge d’ajouter un certificat racine au magasin de confiance iOS, donc une configuration Personal VPN ne peut référencer que du matériel auquel l’appareil fait déjà confiance : une chaîne établie par un profil installé, par la gestion de l’appareil ou par une AC à laquelle le système fait confiance pour d’autres raisons. Une app qui fabrique sa propre AC au premier lancement et attend que le système l’honore échouera à la validation à l’intérieur du fournisseur, où l’interface de l’app ne peut pas voir la raison.

Traitez cette frontière comme la contrainte de conception qu’elle est et livrez l’ancre de confiance avec le profil d’inscription. Gardez l’authentification VPN séparée de la décision de confiance, car elles échouent à des endroits différents et sont déboguées par des personnes différentes.

Les compteurs viennent de l’interface, pas de l’API VPN

Le gestionnaire expose l’état et la configuration, et rien qui compte les octets par tunnel. Si vous avez besoin de chiffres d’usage, appelez getifaddrs() et lisez if_data pour l’interface concernée : en0 pour le Wi-Fi, les interfaces pdp_ip pour le cellulaire et un périphérique utun pour le tunnel lui-même. Les compteurs de utun décrivent le trafic interne et incluent la surcharge du protocole.

Ces chiffres sont des estimations, pas une comptabilité par app, et ils se réinitialisent quand l’interface est recréée. Étiquetez-les ainsi dans l’interface, sinon quelqu’un s’en servira pour facturer un client. Si vous avez besoin de chiffres par app, il vous faut un mécanisme entièrement différent, pas une meilleure lecture de ces champs.

Le nettoyage appartient à stopVPNTunnel, pas à la terminaison

applicationWillTerminate vous donne quelques secondes avant que le système puisse tuer le processus, ce qui n’est pas un endroit fiable pour démonter une connexion. Envoyez l’arrêt à stopVPNTunnel() ou gérez-le dans le rappel d’arrêt du fournisseur, et conservez l’identifiant de gestionnaire ou de configuration avec lequel vous avez démarré pour que l’arrêt cible cette connexion plutôt que toutes les configurations de l’appareil.

Stockez cet identifiant à côté du profil auquel il appartient, car un objet gestionnaire n’enregistre pas quelle configuration l’utilisateur a choisie. Et rappelez-vous qu’une connexion montée par On Demand n’a aucun appel d’app derrière elle, donc un écran d’état doit quand même charger les préférences avant de dessiner quoi que ce soit.

Articles similaires