Zaštita VpnService ne uspijeva kad je drugi VPN već aktivan
Android dopušta jedno VPN sučelje po korisniku. Provedite provjere prepare, establish, protect i vlasništva redom, pa odaberite ukidanje ili izuzimanje po aplikaciji.
Vaša aplikacija poziva VpnService.prepare, dobiva null, gradi tunel i deskriptor izgleda valjano, ali ništa ne dolazi do poslužitelja. Ili prepare vrati Intent, korisnik ga odobri, a establish i dalje ne uspijeva. Oba ishoda obično znače isto: druga aplikacija već posjeduje VPN sučelje za ovog korisnika, a vaši pozivi protect označavaju sockete koje vaš tunel ne može koristiti. Prođite kroz provjere ispod redom i zaustavite se na prvoj koja ne uspije, jer kasniji korak ne može popraviti raniji.
Provjerite VPN mjesto prije nego što dirate protect
Android daje svakom korisniku točno jedno aktivno VPN sučelje. To je pravilo platforme, a ne dopuštenje koje vaša aplikacija može zatražiti, pa dva klijenta koja oba žele biti tunel ne mogu postojati zajedno.
Uvijek uključen VPN postavljen za drugu aplikaciju najteži je slučaj, jer sustav drži tu aplikaciju povezanom i odbija preuzimanje. Filter prometa poput AdGuarda, korporativni klijent ili potrošački VPN može držati mjesto dok je korisnik zaboravio da je instaliran. Zamolite korisnika da otvori Postavke i pročita stavku navedenu pod VPN; ime tamo je vlasnik s kojim se natječete. Dok ta stavka nije isključena ili postavka uvijek uključeno nije obrisana, vaš VpnService ne može postati sučelje.
Korak 1: pročitajte što prepare vraća
VpnService.prepare odgovara na jedno pitanje: smije li ova aplikacija postati VPN? Vraća null kad privola već postoji, a Intent kad je korisnik tek treba odobriti, što morate pokrenuti s startActivityForResult. Tretirajte vraćeni null kao dopuštenje za nastavak, ne kao dokaz da će establish uspjeti.
Intent intent = VpnService.prepare(context);
if (intent != null) {
startActivityForResult(intent, REQUEST_VPN);
}Ako se putanja Intent vrati i korisnik je odbio, prepare će nastaviti vraćati Intent u sljedećem pokušaju. Zabilježite kod rezultata umjesto ponavljanja u petlji, jer odbijena privola je odluka korisnika, a ne prolazna pogreška.
Korak 2: potvrdite da sučelje pripada vašoj aplikaciji
Nakon establish, ParcelFileDescriptor koji primite dokaz je vašeg vlasništva i vrijedi ga provjeriti prije bilo kakvog rada sa socketima. Deskriptor koji je null ili već zatvoren znači da sučelje nikad nije stvoreno, čak i ako se iznimka nije pojavila. Usporedite ime sučelja iz buildera s onim koje sustav prijavljuje za aktivni VPN, koristeći dumpsys na testnom uređaju. Ako se razlikuju, tunel koji ste izgradili nije tunel koji nosi promet.
adb shell dumpsys connectivityIsta neusklađenost objašnjava ikonu povezanosti s mrtvim prometom. Sustav prikazuje tunel druge aplikacije, dok vaš kod drži deskriptor sučelja koje ne posjeduje.
Korak 3: pozovite protect prije connect
Kad sučelje postoji, sockete koje vaša aplikacija otvori sustav podrazumijevano usmjerava u tunel. Vanjski transport ne može slijediti 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 ignoriranje toga način je da završite sa socketom koji izgleda zdravo i ne nosi ništa.
Pozovite protect prije connect ili bind, nikada poslije. Zaštita mora biti na mjestu prije nego se socket poveže ili veže, jer je povezani socket već odabrao rutu i kasniji protect ga ne pomiče pouzdano. Ako protect vrati false, zaustavite se i ponovno provjerite vlasništvo: ne uspijeva 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 željeti 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 prije 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 dopušta dvjema aplikacijama da dijele VPN mjesto, pa popravak pripada stanju uređaja. Ponudite korisniku dvije konkretne putanje: isključite ili obrišite uvijek 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 dopušta vašoj aplikaciji da koristi običnu vezu, što je često ishod koji korisnik zapravo želi.
Upišite obje putanje u svoju dokumentaciju za podršku, jer korisnici rijetko znaju koja aplikacija posjeduje VPN. Zamolite ih da pošalju ime aktivnog VPN-a iz Postavki i rezultate prepare, establish i protect iz vaših zapisa, i možete u minuti reći koji je korak neuspjeo. Zatim provjerite stvarnim prometom: traženje imena, TCP vezu i jedan zahtjev kroz tunel, a ne samo ikonu povezanosti. Ispravan deskriptor i zeleni indikator dokazuju vlasništvo u najboljem slučaju, nikada da je transport preživio pravila rutiranja.
Ako sve četiri provjere prođu i promet i dalje umire, vlasništvo više nije vaš problem. Prijeđite na rutiranje i dostupnost poslužitelja, i testirajte iste vjerodajnice s uređaja bez instalirane VPN aplikacije.