Zaštita VpnService ne uspeva kad je drugi VPN već aktivan
Android dozvoljava jedno VPN sučelje po korisniku. Izvedite provere prepare, establish, protect i vlasništva po redu, pa izaberite ukidanje ili izuzimanje po aplikaciji.
Vaša aplikacija poziva VpnService.prepare, dobija null, gradi tunel i deskriptor izgleda ispravno, ali ništa ne stiže do servera. Ili prepare vrati Intent, korisnik ga odobri, a establish i dalje ne uspeva. Oba ishoda obično znače isto: druga aplikacija već poseduje VPN sučelje za ovog korisnika, a vaši pozivi protect označavaju sockete koje vaš tunel ne može koristiti. Prođite kroz provere ispod po redu i zaustavite se na prvoj koja ne uspe, jer kasniji korak ne može popraviti raniji.
Proverite VPN mesto pre nego što dirate protect
Android daje svakom korisniku tačno jedno aktivno VPN sučelje. To je pravilo platforme, a ne dozvola koju vaša aplikacija može zatražiti, pa dva klijenta koja oba žele biti tunel ne mogu postojati zajedno.
Uvek uključen VPN postavljen za drugu aplikaciju najteži je slučaj, jer sistem drži tu aplikaciju povezanom i odbija preuzimanje. Filter saobraćaja kao AdGuard, korporativni klijent ili potrošački VPN mogu držati mesto dok je korisnik zaboravio da je instaliran. Zamolite korisnika da otvori Podešavanja i pročita stavku navedenu pod VPN; ime tamo je vlasnik s kojim se takmičite. Dok ta stavka nije isključena ili postavka uvek uključeno nije obrisana, vaš VpnService ne može postati sučelje.
Korak 1: pročitajte šta prepare vraća
VpnService.prepare odgovara na jedno pitanje: sme li ova aplikacija postati VPN? Vraća null kad privola već postoji, a Intent kad korisnik tek treba da je odobri, što morate pokrenuti sa startActivityForResult. Tretirajte vraćeni null kao dozvolu za nastavak, ne kao dokaz da će establish uspeti.
Intent intent = VpnService.prepare(context);
if (intent != null) {
startActivityForResult(intent, REQUEST_VPN);
}Ako se putanja Intent vrati i korisnik je odbio, prepare će nastaviti da vraća Intent u sledećem pokušaju. Zabeležite kod rezultata umesto ponavljanja u petlji, jer odbijena privola je odluka korisnika, a ne prolazna greška.
Korak 2: potvrdite da sučelje pripada vašoj aplikaciji
Nakon establish, ParcelFileDescriptor koji primite dokaz je vašeg vlasništva i vredi ga proveriti pre bilo kakvog rada sa socketima. Deskriptor koji je null ili već zatvoren znači da sučelje nikad nije stvoreno, čak i ako se izuzetak nije pojavio. Uporedite ime sučelja iz buildera s onim koje sistem prijavljuje za aktivni VPN, koristeći dumpsys na testnom uređaju. Ako se razlikuju, tunel koji ste izgradili nije tunel koji nosi saobraćaj.
adb shell dumpsys connectivityIsta neusklađenost objašnjava ikonu povezanosti s mrtvim saobraćajem. Sistem prikazuje tunel druge aplikacije, dok vaš kod drži deskriptor sučelja koje ne poseduje.
Korak 3: pozovite protect pre connect
Kad sučelje postoji, sockete koje vaša aplikacija otvori sistem podrazumevano usmerava u tunel. Spoljni transport ne može slediti to pravilo jer bi se inkapsulirani paket vratio u sučelje, pa VpnService protect označava socket da zaobiđe VPN i koristi osnovnu mrežu. Poziv vraća boolean, a ignorisanje toga način je da završite sa socketom koji izgleda zdravo i ne nosi ništa.
Pozovite protect pre connect ili bind, nikada posle. Zaštita mora biti na mestu pre nego se socket poveže ili veže, jer je povezani socket već odabrao rutu i kasniji protect ga ne pomera pouzdano. Ako protect vrati false, zaustavite se i ponovo proverite vlasništvo: ne uspeva kad sučelje ne pripada vašoj aplikaciji, što je upravo stanje koje drugi VPN ostavlja. Povratak false signal je da se vratite na Korak 1, a ne da ponovite isti socket.
DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
return;
}
socket.connect(serverAddress, serverPort);Korak 4: vežite proces kad je socket nezgodan
Neki transporti nisu jedan socket: biblioteka može otvoriti nekoliko ili možete želeti da svaka veza u pomoćnom procesu koristi isti izlaz. ConnectivityManager.bindProcessToNetwork veže proces za odabranu mrežu, a stariji setProcessDefaultNetwork radio je isti posao pre nego što je ukinut. Zatražite osnovnu vezu s NET_CAPABILITY_NOT_VPN kako bi vezivanje odabralo mrežu ispod vašeg tunela, a ne sam tunel.
NetworkRequest request = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();Vezivanje procesa nije drugi VPN. Ono odabire izlaz za taj proces, što je korisno za vaš transport i beskorisno za pokretanje dva tunela odjednom.
Popravite korisnikov uređaj, a ne putanju koda
Nema API-ja koji dozvoljava dvema aplikacijama da dele VPN mesto, pa popravka pripada stanju uređaja. Ponudite korisniku dve konkretne putanje: isključite ili obrišite uvek uključeno za drugu aplikaciju kako bi vaš prepare mogao preuzeti, ili otvorite postavke po aplikaciji te aplikacije i izuzmite svoju aplikaciju iz njenog tunela. Izuzimanje održava filter aktivnim za sve ostalo i dozvoljava vašoj aplikaciji da koristi običnu vezu, što je često ishod koji korisnik zaista želi.
Upišite obe putanje u svoju dokumentaciju za podršku, jer korisnici retko znaju koja aplikacija poseduje VPN. Zamolite ih da pošalju ime aktivnog VPN-a iz Podešavanja i rezultate prepare, establish i protect iz vaših zapisa, i možete u minuti reći koji je korak neuspeo. Zatim proverite stvarnim saobraćajem: traženje imena, TCP vezu i jedan zahtev kroz tunel, a ne samo ikonu povezanosti. Ispravan deskriptor i zeleni indikator dokazuju vlasništvo u najboljem slučaju, nikada da je transport preživeo pravila rutiranja.
Ako sve četiri provere prođu i saobraćaj i dalje umire, vlasništvo više nije vaš problem. Pređite na rutiranje i dostupnost servera, i testirajte iste akreditive s uređaja bez instalirane VPN aplikacije.