Protecția VpnService eșuează când un alt VPN este deja activ
Android permite o singură interfață VPN per utilizator. Rulați verificările prepare, establish, protect și de proprietate în ordine, apoi alegeți revocarea sau excluderea per aplicație.
Aplicația dvs. apelează VpnService.prepare, primește null, construiește un tunel și descriptorul pare valid, dar nimic nu ajunge la server. Sau prepare returnează un Intent, utilizatorul îl aprobă, iar establish eșuează în continuare. Ambele rezultate înseamnă de obicei același lucru: o altă aplicație deține deja interfața VPN pentru acest utilizator, iar apelurile protect marchează socketuri pe care tunelul dvs. nu le poate folosi. Parcurgeți verificările de mai jos în ordine și opriți-vă la prima care eșuează, pentru că un pas ulterior nu poate repara unul anterior.
Verificați locul VPN înainte de a atinge protect
Android oferă fiecărui utilizator exact o interfață VPN activă. Este o regulă a platformei, nu o permisiune pe care aplicația dvs. o poate cere, așa că doi clienți care vor amândoi să fie tunelul nu pot coexista.
Un VPN mereu activ configurat pentru altă aplicație este cel mai greu caz, pentru că sistemul ține acea aplicație conectată și refuză o preluare. Un filtru de trafic precum AdGuard, un client corporate sau un VPN de consum pot ține locul în timp ce utilizatorul a uitat că este instalat. Rugați utilizatorul să deschidă Setările și să citească intrarea listată la VPN; numele de acolo este proprietarul cu care concurați. Până când acea intrare nu este deconectată sau setarea mereu activ nu este ștearsă, VpnService al dvs. nu poate deveni interfața.
Pasul 1: citiți ce returnează prepare
VpnService.prepare răspunde la o singură întrebare: poate această aplicație să devină VPN? Returnează null când consimțământul există deja, și un Intent când utilizatorul trebuie încă să îl aprobe, pe care trebuie să îl lansați cu startActivityForResult. Tratați un retur null ca permisiune de a continua, nu ca dovadă că establish va reuși.
Intent intent = VpnService.prepare(context);
if (intent != null) {
startActivityForResult(intent, REQUEST_VPN);
}Dacă calea Intent revine și utilizatorul a refuzat, prepare va continua să returneze un Intent la următoarea încercare. Înregistrați codul rezultatului în loc să reîncercați într-o buclă, pentru că un consimțământ refuzat este o decizie a utilizatorului, nu o eroare temporară.
Pasul 2: confirmați că interfața aparține aplicației dvs.
După establish, ParcelFileDescriptor primit este dovada proprietății, și merită verificat înainte de orice lucru cu socketuri. Un descriptor null sau deja închis înseamnă că interfața nu a fost niciodată creată, chiar dacă nu a apărut nicio excepție. Comparați numele interfeței din builder cu cel pe care sistemul îl raportează pentru VPN-ul activ, folosind dumpsys pe un dispozitiv de test. Dacă diferă, tunelul pe care l-ați construit nu este tunelul care transportă trafic.
adb shell dumpsys connectivityAceeași nepotrivire explică o pictogramă conectată cu trafic mort. Sistemul afișează tunelul celeilalte aplicații, în timp ce codul dvs. ține un descriptor pentru o interfață pe care nu o deține.
Pasul 3: apelați protect înainte de connect
Odată ce interfața există, socketurile pe care aplicația le deschide sunt rutate în tunel implicit. Transportul exterior nu poate urma această regulă, altfel pachetul încapsulat s-ar întoarce în interfață, de aceea VpnService protect marchează un socket pentru a ocoli VPN-ul și a folosi rețeaua de dedesubt. Apelul returnează un boolean, iar ignorarea lui este modul în care ajungeți cu un socket care pare sănătos și nu transportă nimic.
Apelați protect înainte de connect sau bind, niciodată după. Protecția trebuie să fie la loc înainte ca socketul să fie conectat sau legat, pentru că un socket conectat și-a ales deja ruta, iar un protect ulterior nu îl mută în mod fiabil. Dacă protect returnează false, opriți-vă și verificați din nou proprietatea: eșuează când interfața nu aparține aplicației dvs., ceea ce este exact starea pe care o lasă alt VPN. Un retur false este un semnal să reveniți la Pasul 1, nu să reîncercați același socket.
DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
return;
}
socket.connect(serverAddress, serverPort);Pasul 4: legați un proces când un socket este incomod
Unele transporturi nu sunt un singur socket: o bibliotecă poate deschide mai multe, sau poate doriți ca fiecare conexiune dintr-un proces auxiliar să folosească aceeași ieșire. ConnectivityManager.bindProcessToNetwork leagă procesul de o rețea aleasă, iar vechiul setProcessDefaultNetwork făcea același lucru înainte de a deveni învechit. Cereți conexiunea de dedesubt cu NET_CAPABILITY_NOT_VPN, astfel încât legarea să selecteze rețeaua de sub tunelul dvs. și nu tunelul însuși.
NetworkRequest request = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();Legarea unui proces nu este un al doilea VPN. Alege o ieșire pentru acel proces, ceea ce este util pentru transportul dvs. și inutil pentru a rula două tuneluri simultan.
Reparați dispozitivul utilizatorului, nu calea codului
Nu există niciun API care să permită două aplicații să partajeze locul VPN, așa că remedierea ține de starea dispozitivului. Oferiți utilizatorului două căi concrete: deconectați sau ștergeți setarea mereu activ pentru cealaltă aplicație, ca prepare al dvs. să poată prelua, sau deschideți setările per aplicație ale acelei aplicații și excludeți-vă aplicația din tunelul ei. Excluderea menține filtrul funcționând pentru tot restul și permite aplicației dvs. să folosească conexiunea obișnuită, care este adesea rezultatul pe care utilizatorul îl vrea cu adevărat.
Scrieți ambele căi în documentația de suport, pentru că utilizatorii știu rareori ce aplicație deține VPN-ul. Rugați-i să trimită numele VPN-ului activ din Setări și rezultatele prepare, establish și protect din jurnalele dvs., și puteți spune într-un minut care pas a eșuat. Apoi verificați cu trafic real: o interogare de nume, o conexiune TCP și o cerere prin tunel, nu doar o pictogramă conectată. Un descriptor valid și un indicator verde dovedesc cel mult proprietatea, niciodată că transportul a supraviețuit regulilor de rutare.
Dacă toate cele patru verificări trec și traficul tot moare, proprietatea nu mai este problema dumneavoastră. Treceți la rutare și accesibilitatea serverului, și testați aceleași credențiale de pe un dispozitiv fără nicio aplicație VPN instalată.