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 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 po redu 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 dozvola koju 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 sistem drži tu aplikaciju povezanom i odbija preuzimanje. Filter prometa kao AdGuard, 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 šta prepare vraća

VpnService.prepare odgovara na jedno pitanje: smije li ova aplikacija postati VPN? Vraća null kada privola već postoji, a Intent kada je korisnik tek treba odobriti, što morate pokrenuti s startActivityForResult. Tretirajte vraćeni null kao dozvolu 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 greš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 nikada 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 promet.

adb shell dumpsys connectivity

Ista neusklađenost objašnjava ikonu povezanosti s mrtvim prometom. Sistem prikazuje tunel druge aplikacije, dok vaš kod drži deskriptor sučelja koje ne posjeduje.

Korak 3: pozovite protect prije connect

Kada sučelje postoji, sockete koje vaša aplikacija otvori sistem 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 ignorisanje toga je način da završite sa socketom koji izgleda zdrav i ne nosi ništa.

Pozovite protect prije connect ili bind, nikada poslije. Zaštita mora biti na mjestu prije nego što se socket poveže ili veže, jer je povezani socket već odabrao rutu i kasniji protect ga ne pomjera pouzdano. Ako protect vrati false, zaustavite se i ponovo provjerite vlasništvo: ne uspijeva kada 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 kada 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 servera, i testirajte iste vjerodajnice s uređaja bez instalirane VPN aplikacije.

Povezani članci