Ochrana VpnService zlyhá, keď je už aktívna iná VPN
Android povoľuje jedno rozhranie VPN na používateľa. Vykonajte kontroly prepare, establish, protect a vlastníctva v poradí a potom zvoľte odvolanie alebo vylúčenie podľa aplikácie.
Vaša aplikácia zavolá VpnService.prepare, dostane null, zostaví tunel a deskriptor vyzerá platne, no na server sa nič nedostane. Alebo prepare vráti Intent, používateľ ho schváli a establish napriek tomu zlyhá. Oba výsledky zvyčajne znamenajú to isté: rozhranie VPN pre tohto používateľa už vlastní iná aplikácia a vaše volania protect označujú sockety, ktoré váš tunel nemôže použiť. Prejdite nižšie uvedené kontroly v poradí a zastavte sa pri prvej, ktorá zlyhá, pretože neskorší krok nemôže opraviť skorší.
Pred dotykom protect skontrolujte slot VPN
Android dáva každému používateľovi práve jedno aktívne rozhranie VPN. Je to pravidlo platformy, nie povolenie, o ktoré môže vaša aplikácia požiadať, takže dvaja klienti, ktorí chcú byť tunelom, nemôžu existovať vedľa seba.
Vždy zapnutá VPN nastavená pre inú aplikáciu je najťažší prípad, pretože systém drží tú aplikáciu pripojenú a odmieta prevzatie. Filter prevádzky, ako je AdGuard, firemný klient alebo spotrebiteľská VPN, môže držať slot, kým používateľ zabudol, že je nainštalovaný. Požiadajte používateľa, aby otvoril Nastavenia a prečítal položku uvedenú pod VPN; meno tam je vlastník, s ktorým súťažíte. Kým tá položka nie je odpojená alebo nastavenie vždy zapnuté nie je vymazané, váš VpnService sa nemôže stať rozhraním.
Krok 1: prečítajte si, čo prepare vracia
VpnService.prepare odpovedá na jedinú otázku: smie sa táto aplikácia stať VPN? Vracia null, keď už súhlas existuje, a Intent, keď ho používateľ ešte musí schváliť, čo musíte spustiť cez startActivityForResult. Berte vrátený null ako povolenie pokračovať, nie ako dôkaz, že establish uspeje.
Intent intent = VpnService.prepare(context);
if (intent != null) {
startActivityForResult(intent, REQUEST_VPN);
}Ak sa cesta Intent vráti a používateľ odmietol, prepare bude pri ďalšom pokuse vracať Intent ďalej. Zaznamenajte kód výsledku namiesto opakovania v slučke, pretože odmietnutý súhlas je rozhodnutie používateľa, nie prechodná chyba.
Krok 2: potvrďte, že rozhranie patrí vašej aplikácii
Po establish je ParcelFileDescriptor, ktorý dostanete, vaším dokladom vlastníctva a vyplatí sa ho skontrolovať pred akoukoľvek prácou so socketmi. Deskriptor, ktorý je null alebo už zatvorený, znamená, že rozhranie nikdy nevzniklo, aj keď sa neobjavila žiadna výnimka. Porovnajte názov rozhrania z builderu s tým, ktorý systém hlási pre aktívnu VPN, pomocou dumpsys na testovacom zariadení. Ak sa líšia, tunel, ktorý ste zostavili, nie je tunel, ktorý prenáša prevádzku.
adb shell dumpsys connectivityRovnaký nesúlad vysvetľuje pripojenú ikonu s mŕtvou prevádzkou. Systém zobrazuje tunel druhej aplikácie, zatiaľ čo váš kód drží deskriptor rozhrania, ktoré nevlastní.
Krok 3: zavolajte protect pred connect
Keď rozhranie existuje, sockety, ktoré vaša aplikácia otvorí, sa predvolene smerujú do tunela. Vonkajší transport nemôže toto pravidlo dodržať, inak by sa zapuzdrený paket vrátil do rozhrania, preto VpnService protect označí socket, aby obišiel VPN a použil podkladovú sieť. Volanie vracia boolean a jeho ignorovanie je spôsob, ako skončiť so socketom, ktorý vyzerá zdravo a nič neprenáša.
Volajte protect pred connect alebo bind, nikdy po ňom. Ochrana musí byť na mieste skôr, než je socket pripojený alebo zviazaný, pretože pripojený socket si už vybral trasu a neskorší protect ho spoľahlivo nepresunie. Ak protect vráti false, zastavte sa a znova skontrolujte vlastníctvo: zlyháva, keď rozhranie nepatrí vašej aplikácii, čo je presne stav, ktorý iná VPN zanechá. Vrátenie false je signál vrátiť sa ku Kroku 1, nie skúšať znova ten istý socket.
DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
return;
}
socket.connect(serverAddress, serverPort);Krok 4: zviažte proces, keď je socket nepohodlný
Niektoré transporty nie sú jediný socket: knižnica môže otvoriť niekoľko alebo môžete chcieť, aby každé spojenie v pomocnom procese použilo rovnaký výstup. ConnectivityManager.bindProcessToNetwork zviaže proces so zvolenou sieťou a starší setProcessDefaultNetwork robil to isté, kým nebol zrušený. Vyžiadajte podkladové spojenie s NET_CAPABILITY_NOT_VPN, aby väzba vybrala sieť pod vaším tunelom, nie tunel samotný.
NetworkRequest request = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();Zviazanie procesu nie je druhá VPN. Vyberá výstup pre tento proces, čo je užitočné pre váš transport a neužitočné pre prevádzku dvoch tunelov naraz.
Opravte zariadenie používateľa, nie cestu v kóde
Neexistuje API, ktoré by dvom aplikáciám umožnilo zdieľať slot VPN, takže oprava patrí k stavu zariadenia. Ponúknite používateľovi dve konkrétne cesty: odpojte alebo vymažte vždy zapnuté pre druhú aplikáciu, aby váš prepare mohol prevziať, alebo otvorte nastavenia danej aplikácie pre jednotlivé aplikácie a vylúčte svoju aplikáciu z jej tunela. Vylúčenie udrží filter v chode pre všetko ostatné a umožní vašej aplikácii použiť bežné pripojenie, čo je často výsledok, ktorý používateľ skutočne chce.
Zapíšte obe cesty do svojej podpornej dokumentácie, pretože používatelia málokedy vedia, ktorá aplikácia VPN vlastní. Požiadajte ich, aby poslali názov aktívnej VPN z Nastavení a výsledky prepare, establish a protect zo svojich logov, a do minúty poviete, ktorý krok zlyhal. Potom overte skutočnou prevádzkou: vyhľadanie názvu, TCP spojenie a jedna požiadavka cez tunel, nie len pripojenú ikonu. Platný deskriptor a zelený indikátor preukazujú vlastníctvo nanajvýš, nikdy to, že transport prežil pravidlá smerovania.
Ak všetky štyri kontroly prejdú a prevádzka stále umiera, vlastníctvo už nie je váš problém. Prejdite na smerovanie a dostupnosť servera a otestujte rovnaké prihlásenie zo zariadenia bez nainštalovanej VPN aplikácie.