Aplicația ta spune deconectat, bara de meniu spune conectat, iar traficul curge vizibil. Acest decalaj nu este o condiție de cursă pe care o repari cu o reîncercare: aplicația și tunelul sunt procese separate, iar obiectul pe care îl citește codul tău este o vedere în cache a unei configurații pe care sistemul o stochează. Suprafața Personal VPN a Apple este un API de control, nu o cale de date, iar cea mai mare parte a comportamentului ei ciudat decurge din acest singur fapt. Iată ce se întâmplă de fapt la fiecare apel.

Obiectul managerului este o vedere în cache, nu tunelul

NETunnelProviderManager este populat dintr-un depozit de preferințe deținut de sistem, așa că proprietățile sale nu înseamnă nimic până când loadFromPreferences nu se termină. Dacă citești connection.status înainte de asta, obții o valoare implicită, și de aceea prima reîmprospătare a stării după o repornire raportează deconectat în timp ce tunelul rulează. Încarcă mai întâi, apoi citește starea și configurația protocolului din același handler de finalizare. Urmărește modificările ulterioare prin NEVPNStatusDidChange în loc să interoghezi, și observă că notificarea se declanșează și pentru conexiunile pornite de On Demand, unde nu există niciun apel al aplicației care să returneze ceva. La fel se întâmplă cu setările protocolului: sunt citite din configurația încărcată, nu din ce a transmis aplicația.

Tunelul în sine trăiește într-un proces furnizor Network Extension, așa că aplicația poate pierde propria stare fără a pierde conexiunea. Tot ce afișează interfața trebuie reconstruit din manager, nu reținut din sesiunea anterioară.

Ordinea de pornire: salvează, încarcă, apoi pornește

O conexiune bazată pe furnizor necesită trei pași în ordine. Scrie configurația furnizorului și apelează saveToPreferences, încarcă managerul astfel încât configurația salvată să fie cea pe care o ții, apoi pornește prin NETunnelProviderSession.startVPNTunnel(). Dacă sari peste salvare, pornirea nu are ce porni; dacă încarci înainte de salvare, managerul reflectă încă vechea configurație.

Valoarea booleană returnată de startVPNTunnel spune doar că cererea a fost acceptată. Eșecurile la nivel de furnizor vin mai târziu ca schimbare de stare în deconectat, pe care o observi prin notificarea NEVPNStatusDidChange, iar handlerul notificării trebuie să recitească connection.status după loadFromPreferences pentru că valoarea din cache poate fi încă cea veche. Eșecurile la nivel de cerere se întorc imediat din forma care aruncă excepții a apelului, sau prin parametrul de ieșire pentru eroare al startVPNTunnelAndReturnError:. Codul care tratează valoarea returnată drept rezultat raportează o conexiune care nu s-a stabilit niciodată. Celălalt eșec silențios este un identificator de pachet al furnizorului care nu corespunde extensiei instalate: cererea este respinsă, și nimic din mesaj nu indică identificatorul. Dacă identificatorul este greșit, cererea eșuează de obicei imediat, înainte să apară vreo linie de jurnal a furnizorului.

Reîncărcarea preferințelor după o pornire este ceea ce face ecranele de stare corecte. Managerul nu îți împinge o configurație nouă atunci când sistemul o schimbă.

Materialul de încredere vine prin profil, niciodată din codul aplicației

O aplicație nu are nicio modalitate acceptată de a adăuga un certificat rădăcină în depozitul de încredere iOS, așa că o configurație Personal VPN poate referi doar material în care dispozitivul are deja încredere: un lanț stabilit de un profil instalat, de administrarea dispozitivului sau de o CA în care sistemul are încredere din alte motive. O aplicație care își creează propriul CA la prima pornire și așteaptă ca sistemul să îl accepte va eșua validarea în interiorul furnizorului, unde interfața aplicației nu poate vedea motivul.

Tratează acea graniță ca constrângerea de proiectare care este și livrează ancora de încredere împreună cu profilul de înrolare. Ține autentificarea VPN separată de decizia de încredere, pentru că eșuează în locuri diferite și sunt depistate de oameni diferiți.

Contoarele vin de la interfață, nu de la API-ul VPN

Managerul expune starea și configurația, și nimic care să numere octeți pe tunel. Dacă ai nevoie de cifre de utilizare, apelează getifaddrs() și citește if_data pentru interfața relevantă: en0 pentru Wi-Fi, interfețele pdp_ip pentru mobil și un dispozitiv utun pentru tunelul propriu-zis. Contoarele utun descriu traficul interior și includ suprasarcina protocolului.

Aceste cifre sunt estimări, nu contabilitate pe aplicație, și se resetează când interfața este recreată. Etichetează-le astfel în interfață, altfel cineva le va folosi pentru a factura un client. Dacă ai nevoie de cifre pe aplicație, ai nevoie de un mecanism complet diferit, nu de o citire mai bună a acestor câmpuri.

Curățarea aparține de stopVPNTunnel, nu de terminare

applicationWillTerminate îți dă câteva secunde înainte ca sistemul să poată ucide procesul, iar acesta nu este un loc de încredere pentru a demonta o conexiune. Trimite oprirea către stopVPNTunnel() sau trateaz-o în callback-ul de oprire al furnizorului, și păstrează identificatorul managerului sau al configurației cu care ai pornit, astfel încât oprirea să vizeze acea conexiune în loc de fiecare configurație de pe dispozitiv.

Stochează acel identificator lângă profilul căruia îi aparține, deoarece un obiect manager nu înregistrează ce configurație a selectat utilizatorul. Și reține că o conexiune ridicată de On Demand nu are niciun apel al aplicației în spate, așa că un ecran de stare tot trebuie să încarce preferințele înainte de a desena ceva.

Articole similare