Din app anropar VpnService.prepare, får null tillbaka, bygger en tunnel och deskriptorn ser giltig ut, men ingenting når servern. Eller prepare returnerar en Intent, användaren godkänner den och establish misslyckas ändå. Båda utfallen betyder oftast samma sak: en annan app äger redan VPN-gränssnittet för den här användaren, och dina protect-anrop markerar sockets som tunneln inte kan använda. Gå igenom kontrollerna nedan i ordning och stanna vid den första som misslyckas, eftersom ett senare steg inte kan reparera ett tidigare.

Kontrollera VPN-platsen innan du rör protect

Android ger varje användare exakt ett aktivt VPN-gränssnitt. Det är en plattformsregel, inte en behörighet som din app kan begära, så två klienter som båda vill vara tunneln kan inte samexistera.

Ett alltid på-VPN inställt för en annan app är det svåraste fallet, eftersom systemet håller den appen ansluten och avvisar ett övertagande. Ett trafikfilter som AdGuard, en företagsklient eller en konsument-VPN kan hålla platsen medan användaren har glömt att den är installerad. Be användaren öppna Inställningar och läsa posten som listas under VPN; namnet där är ägaren du konkurrerar med. Tills den posten är frånkopplad eller inställningen alltid på är rensad kan din VpnService inte bli gränssnittet.

Steg 1: läs vad prepare returnerar

VpnService.prepare svarar på en enda fråga: får den här appen bli VPN:en? Den returnerar null när samtycke redan finns, och en Intent när användaren fortfarande måste godkänna den, som du måste starta med startActivityForResult. Behandla en null-retur som tillstånd att fortsätta, inte som bevis på att establish kommer att lyckas.

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

Om Intent-vägen kommer tillbaka och användaren avböjde kommer prepare att fortsätta returnera en Intent vid nästa försök. Logga resultatkoden i stället för att försöka igen i en loop, eftersom ett avböjt samtycke är användarens beslut, inte ett tillfälligt fel.

Steg 2: bekräfta att gränssnittet tillhör din app

Efter establish är den ParcelFileDescriptor du får ditt bevis på ägande, och det är värt att kontrollera innan något socket-arbete. En deskriptor som är null eller redan stängd betyder att gränssnittet aldrig skapades, även om inget undantag dök upp. Jämför gränssnittsnamnet från byggaren med det systemet rapporterar för det aktiva VPN:et, med dumpsys på en testenhet. Om de skiljer sig är tunneln du byggde inte tunneln som bär trafik.

adb shell dumpsys connectivity

Samma brist på överensstämmelse förklarar en ansluten ikon med död trafik. Systemet visar den andra appens tunnel, medan din kod håller en deskriptor för ett gränssnitt den inte äger.

Steg 3: anropa protect före connect

När gränssnittet finns dirigeras sockets som din app öppnar in i tunneln som standard. Den yttre transporten kan inte följa den regeln, annars skulle det inkapslade paketet gå tillbaka in i gränssnittet, därför VpnService protect markerar en socket för att kringgå VPN:et och använda det underliggande nätverket. Anropet returnerar en boolean, och att ignorera den är hur du hamnar med en socket som ser frisk ut och inte bär något.

Anropa protect före connect eller bind, aldrig efter. Skyddet måste vara på plats innan socketen ansluts eller binds, eftersom en ansluten socket redan har valt sin väg och en senare protect inte flyttar den pålitligt. Om protect returnerar false, stanna och kontrollera ägandet igen: den misslyckas när gränssnittet inte tillhör din app, vilket är precis det tillstånd en annan VPN lämnar efter sig. En false-retur är en signal att gå tillbaka till Steg 1, inte att försöka med samma socket igen.

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

Steg 4: bind en process när en socket är besvärlig

Vissa transporter är inte en enda socket: ett bibliotek kan öppna flera, eller du kanske vill att varje anslutning i en hjälpprocess använder samma utgång. ConnectivityManager.bindProcessToNetwork binder processen till ett valt nätverk, och den äldre setProcessDefaultNetwork gjorde samma sak innan den blev föråldrad. Begär den underliggande anslutningen med NET_CAPABILITY_NOT_VPN så att bindningen väljer nätverket under din tunnel och inte själva tunneln.

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

Att binda en process är inte ett andra VPN. Det väljer en utgång för den processen, vilket är användbart för din transport och oanvändbart för att köra två tunnlar samtidigt.

Rätta användarens enhet, inte kodvägen

Det finns inget API som låter två appar dela VPN-platsen, så rättelsen hör till enhetens tillstånd. Erbjud användaren två konkreta vägar: koppla från eller rensa alltid på för den andra appen så att din prepare kan ta över, eller öppna den appens inställningar per app och uteslut din app från dess tunnel. Undantaget håller filtret igång för allt annat och låter din app använda den vanliga anslutningen, vilket ofta är resultatet användaren faktiskt vill ha.

Skriv båda vägarna i din supportdokumentation, eftersom användare sällan vet vilken app som äger VPN:et. Be dem skicka namnet på det aktiva VPN:et från Inställningar och resultaten av prepare, establish och protect från dina loggar, och du kan inom en minut säga vilket steg som misslyckades. Verifiera sedan med riktig trafik: en namnsökning, en TCP-anslutning och en begäran genom tunneln, inte bara en ansluten ikon. En giltig deskriptor och en grön indikator bevisar som mest ägande, aldrig att transporten överlevde routningsreglerna.

Om alla fyra kontrollerna går igenom och trafiken ändå dör är ägandet inte längre ditt problem. Gå vidare till routning och serverns nåbarhet, och testa samma inloggningsuppgifter från en enhet utan installerad VPN-app.

Relaterade artiklar