Vaše aplikace zavolá VpnService.prepare, dostane null, sestaví tunel a deskriptor vypadá platně, ale na server nic nedorazí. Nebo prepare vrátí Intent, uživatel ho schválí, a establish přesto selže. Oba výsledky obvykle znamenají totéž: rozhraní VPN pro tohoto uživatele už vlastní jiná aplikace a vaše volání protect označují sockety, které váš tunel nemůže použít. Projděte níže uvedené kontroly v pořadí a zastavte se u první, která selže, protože pozdější krok nemůže opravit dřívější.

Než se dotknete protect, zkontrolujte slot VPN

Android dává každému uživateli právě jedno aktivní rozhraní VPN. To je pravidlo platformy, ne oprávnění, o které může vaše aplikace požádat, takže dva klienti, kteří chtějí být tunelem, nemohou existovat vedle sebe.

Vždy zapnutá VPN nastavená pro jinou aplikaci je nejtěžší případ, protože systém drží onu aplikaci připojenou a převzetí odmítá. Filtr provozu, jako je AdGuard, firemní klient nebo spotřebitelská VPN, může slot držet, zatímco uživatel zapomněl, že je nainstalovaný. Požádejte uživatele, aby otevřel Nastavení a přečetl položku uvedenou pod VPN; jméno tam je vlastník, se kterým soutěžíte. Dokud tato položka není odpojena nebo nastavení vždy zapnuto není vymazáno, váš VpnService se nemůže stát rozhraním.

Krok 1: přečtěte si, co prepare vrací

VpnService.prepare odpovídá na jedinou otázku: smí se tato aplikace stát VPN? Vrací null, když už souhlas existuje, a Intent, když jej uživatel ještě musí schválit, což musíte spustit pomocí startActivityForResult. Berte vrácený null jako svolení pokračovat, ne jako důkaz, že establish uspěje.

Intent intent = VpnService.prepare(context);
if (intent != null) {
    startActivityForResult(intent, REQUEST_VPN);
}

Pokud se cesta s Intent vrátí a uživatel odmítl, prepare bude při dalším pokusu vracet Intent dál. Zaznamenejte kód výsledku místo opakování ve smyčce, protože odmítnutý souhlas je rozhodnutí uživatele, ne přechodná chyba.

Krok 2: potvrďte, že rozhraní patří vaší aplikaci

Po establish je ParcelFileDescriptor, který obdržíte, vaším dokladem vlastnictví a vyplatí se ho zkontrolovat před jakoukoli prací se sockety. Deskriptor, který je null nebo už zavřený, znamená, že rozhraní nikdy nevzniklo, i když se neobjevila žádná výjimka. Porovnejte název rozhraní z builderu s tím, který systém hlásí pro aktivní VPN, pomocí dumpsys na testovacím zařízení. Pokud se liší, tunel, který jste sestavili, není tunel, který přenáší provoz.

adb shell dumpsys connectivity

Stejný nesoulad vysvětluje připojenou ikonu s mrtvým provozem. Systém zobrazuje tunel druhé aplikace, zatímco váš kód drží deskriptor rozhraní, které nevlastní.

Krok 3: zavolejte protect před connect

Jakmile rozhraní existuje, sockety, které vaše aplikace otevře, se ve výchozím nastavení směrují do tunelu. Vnější transport nemůže toto pravidlo dodržet, jinak by se zapouzdřený paket vrátil do rozhraní, proto VpnService protect označí socket, aby obešel VPN a použil podkladovou síť. Volání vrací boolean a jeho ignorování je způsob, jak skončit se socketem, který vypadá zdravě a nic nepřenáší.

Volejte protect před connect nebo bind, nikdy po něm. Ochrana musí být na místě dřív, než je socket připojen nebo svázán, protože připojený socket už zvolil svou trasu a pozdější protect ho spolehlivě nepřesune. Pokud protect vrátí false, zastavte se a znovu zkontrolujte vlastnictví: selhává, když rozhraní nepatří vaší aplikaci, což je přesně stav, který jiná VPN zanechá. Vrácení false je signál vrátit se ke Kroku 1, ne zkoušet znovu stejný socket.

DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
    return;
}
socket.connect(serverAddress, serverPort);

Krok 4: svažte proces, když je socket nepohodlný

Některé transporty nejsou jediný socket: knihovna může otevřít několik nebo můžete chtít, aby každé spojení v pomocném procesu použilo stejný výstup. ConnectivityManager.bindProcessToNetwork sváže proces se zvolenou sítí a starší setProcessDefaultNetwork dělal totéž, než byl zrušen. Vyžádejte podkladové spojení s NET_CAPABILITY_NOT_VPN, aby vazba vybrala síť pod vaším tunelem, ne tunel samotný.

NetworkRequest request = new NetworkRequest.Builder()
    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();

Svázání procesu není druhá VPN. Volí výstup pro tento proces, což je užitečné pro váš transport a neužitečné pro provoz dvou tunelů najednou.

Opravte zařízení uživatele, ne cestu v kódu

Neexistuje API, které by dvěma aplikacím umožnilo sdílet slot VPN, takže oprava patří ke stavu zařízení. Nabídněte uživateli dvě konkrétní cesty: odpojte nebo vymažte vždy zapnuto pro druhou aplikaci, aby váš prepare mohl převzít, nebo otevřete nastavení dané aplikace pro jednotlivé aplikace a vylučte svou aplikaci z jejího tunelu. Vyloučení udrží filtr v chodu pro všechno ostatní a umožní vaší aplikaci použít běžné připojení, což je často výsledek, který uživatel skutečně chce.

Zapište obě cesty do své podpůrné dokumentace, protože uživatelé málokdy vědí, která aplikace VPN vlastní. Požádejte je, aby poslali název aktivní VPN z Nastavení a výsledky prepare, establish a protect ze svých logů, a během minuty poznáte, který krok selhal. Poté ověřte skutečným provozem: vyhledání názvu, TCP spojení a jeden požadavek tunelem, ne jen připojenou ikonu. Platný deskriptor a zelený indikátor prokazují vlastnictví v nejlepším případě, nikdy to, že transport přežil pravidla směrování.

Pokud všechny čtyři kontroly projdou a provoz stále umírá, vlastnictví už není váš problém. Přejděte ke směrování a dostupnosti serveru a otestujte stejné přihlašovací údaje ze zařízení bez nainstalované VPN aplikace.

Související články