VpnService-beveiliging mislukt wanneer er al een andere VPN actief is
Android staat één VPN-interface per gebruiker toe. Voer de controles prepare, establish, protect en eigendom op volgorde uit en kies daarna intrekken of uitsluiting per app.
Je app roept VpnService.prepare aan, krijgt null terug, bouwt een tunnel en de descriptor lijkt geldig, maar niets bereikt de server. Of prepare geeft een Intent terug, de gebruiker keurt die goed, en establish mislukt alsnog. Beide uitkomsten betekenen meestal hetzelfde: een andere app bezit al de VPN-interface voor deze gebruiker, en je protect-aanroepen markeren sockets die je tunnel niet kan gebruiken. Loop de controles hieronder in volgorde door en stop bij de eerste die mislukt, want een latere stap kan een eerdere niet repareren.
Controleer de VPN-plek voordat je protect aanraakt
Android geeft elke gebruiker precies één actieve VPN-interface. Dat is een platformregel, geen toestemming die je app kan aanvragen, dus twee clients die allebei de tunnel willen zijn kunnen niet naast elkaar bestaan.
Een altijd actieve VPN die voor een andere app is ingesteld is het moeilijkste geval, omdat het systeem die app verbonden houdt en een overname weigert. Een verkeersfilter zoals AdGuard, een zakelijke client of een consumenten-VPN kan de plek bezetten terwijl de gebruiker vergeten is dat die geïnstalleerd is. Vraag de gebruiker Instellingen te openen en de vermelding onder VPN te lezen; de naam daar is de eigenaar waarmee je concurreert. Tot die vermelding is losgekoppeld of de instelling altijd aan is gewist, kan je VpnService niet de interface worden.
Stap 1: lees wat prepare teruggeeft
VpnService.prepare beantwoordt één vraag: mag deze app de VPN worden? Het geeft null terug wanneer toestemming al bestaat, en een Intent wanneer de gebruiker die nog moet goedkeuren, die je met startActivityForResult moet starten. Behandel een null-teruggave als toestemming om door te gaan, niet als bewijs dat establish zal slagen.
Intent intent = VpnService.prepare(context);
if (intent != null) {
startActivityForResult(intent, REQUEST_VPN);
}Als het Intent-pad terugkomt en de gebruiker heeft geweigerd, blijft prepare bij de volgende poging een Intent teruggeven. Log de resultaatcode in plaats van in een lus opnieuw te proberen, want een geweigerde toestemming is een beslissing van de gebruiker, geen tijdelijke fout.
Stap 2: bevestig dat de interface bij je app hoort
Na establish is de ParcelFileDescriptor die je ontvangt je bewijs van eigendom, en het is de moeite waard die te controleren vóór enig socketwerk. Een descriptor die null is of al gesloten betekent dat de interface nooit is gemaakt, zelfs als er geen uitzondering verscheen. Vergelijk de interfacenaam uit de builder met die het systeem voor de actieve VPN meldt, met dumpsys op een testapparaat. Als ze verschillen, is de tunnel die je bouwde niet de tunnel die verkeer draagt.
adb shell dumpsys connectivityDezelfde mismatch verklaart een verbonden pictogram met dood verkeer. Het systeem toont de tunnel van de andere app, terwijl je code een descriptor vasthoudt voor een interface die hij niet bezit.
Stap 3: roep protect aan vóór connect
Zodra de interface bestaat, worden sockets die je app opent standaard naar de tunnel gerouteerd. Het buitenste transport kan die regel niet volgen, anders zou het ingekapselde pakket terug de interface in lopen, daarom VpnService protect markeert een socket om de VPN te omzeilen en het onderliggende netwerk te gebruiken. De aanroep geeft een boolean terug, en die negeren is hoe je eindigt met een socket die er gezond uitziet en niets draagt.
Roep protect aan vóór connect of bind, nooit erna. De beveiliging moet op zijn plaats zijn voordat de socket wordt verbonden of gebonden, want een verbonden socket heeft zijn route al gekozen en een latere protect verplaatst hem niet betrouwbaar. Als protect false teruggeeft, stop en controleer het eigendom opnieuw: het mislukt wanneer de interface niet bij je app hoort, en dat is precies de toestand die een andere VPN achterlaat. Een false-teruggave is een signaal om terug te gaan naar Stap 1, niet om dezelfde socket opnieuw te proberen.
DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
return;
}
socket.connect(serverAddress, serverPort);Stap 4: bind een proces wanneer een socket lastig is
Sommige transporten zijn niet één socket: een bibliotheek opent er misschien meerdere, of je wilt dat elke verbinding in een hulpproces dezelfde uitgang gebruikt. ConnectivityManager.bindProcessToNetwork bindt het proces aan een gekozen netwerk, en de oudere setProcessDefaultNetwork deed hetzelfde voordat die verouderd raakte. Vraag de onderliggende verbinding aan met NET_CAPABILITY_NOT_VPN zodat de binding het netwerk onder je tunnel kiest en niet de tunnel zelf.
NetworkRequest request = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();Een proces binden is geen tweede VPN. Het kiest een uitgang voor dat proces, wat nuttig is voor je transport en nutteloos voor het draaien van twee tunnels tegelijk.
Repareer het apparaat van de gebruiker, niet het codepad
Er is geen API die twee apps de VPN-plek laat delen, dus de oplossing hoort bij de toestand van het apparaat. Bied de gebruiker twee concrete wegen: koppel de andere app los of wis altijd aan, zodat je prepare kan overnemen, of open de instellingen per app van die app en sluit je app uit van zijn tunnel. De uitsluiting houdt het filter voor al het andere draaiende en laat je app de gewone verbinding gebruiken, wat vaak het resultaat is dat de gebruiker echt wil.
Schrijf beide wegen in je supportdocumentatie, want gebruikers weten zelden welke app de VPN bezit. Vraag ze de naam van de actieve VPN uit Instellingen en de resultaten van prepare, establish en protect uit je logs te sturen, en je kunt binnen een minuut zeggen welke stap mislukte. Controleer daarna met echt verkeer: een naamopzoeking, een TCP-verbinding en één verzoek door de tunnel, niet alleen een verbonden pictogram. Een geldige descriptor en een groene indicator bewijzen hooguit eigendom, nooit dat het transport de routeringsregels heeft overleefd.
Als alle vier controles slagen en het verkeer sterft nog steeds, is eigendom niet langer je probleem. Ga verder naar routering en bereikbaarheid van de server, en test dezelfde inloggegevens vanaf een apparaat zonder geïnstalleerde VPN-app.