VpnService-vernet svikter når en annen VPN allerede er aktiv
Android tillater ett VPN-grensesnitt per bruker. Kjør kontrollene prepare, establish, protect og eierskap i rekkefølge, og velg deretter tilbakekalling eller unntak per app.
Appen din kaller VpnService.prepare, får null tilbake, bygger en tunnel, og deskriptoren ser gyldig ut, men ingenting når serveren. Eller prepare returnerer en Intent, brukeren godkjenner den, og establish feiler fortsatt. Begge utfallene betyr vanligvis det samme: en annen app eier allerede VPN-grensesnittet for denne brukeren, og protect-kallene dine markerer sockets som tunnelen din ikke kan bruke. Gå gjennom kontrollene nedenfor i rekkefølge og stopp ved den første som feiler, fordi et senere trinn ikke kan reparere et tidligere.
Sjekk VPN-plassen før du rører protect
Android gir hver bruker nøyaktig ett aktivt VPN-grensesnitt. Det er en plattformregel, ikke en tillatelse appen din kan be om, så to klienter som begge vil være tunnelen kan ikke eksistere samtidig.
En alltid på VPN satt opp for en annen app er det vanskeligste tilfellet, fordi systemet holder den appen tilkoblet og avviser en overtakelse. Et trafikkfilter som AdGuard, en bedriftsklient eller en forbruker-VPN kan holde plassen mens brukeren har glemt at den er installert. Be brukeren åpne Innstillinger og lese oppføringen som står under VPN; navnet der er eieren du konkurrerer med. Til den oppføringen er koblet fra eller innstillingen alltid på er slettet, kan ikke VpnService bli grensesnittet.
Trinn 1: les hva prepare returnerer
VpnService.prepare svarer på ett spørsmål: kan denne appen bli VPN-en? Den returnerer null når samtykke allerede finnes, og en Intent når brukeren fortsatt må godkjenne den, som du må starte med startActivityForResult. Behandle en null-retur som tillatelse til å fortsette, ikke som bevis på at establish vil lykkes.
Intent intent = VpnService.prepare(context);
if (intent != null) {
startActivityForResult(intent, REQUEST_VPN);
}Hvis Intent-veien kommer tilbake og brukeren avslo, vil prepare fortsette å returnere en Intent ved neste forsøk. Logg resultatkoden i stedet for å prøve igjen i en løkke, fordi et avslått samtykke er en brukerbeslutning, ikke en forbigående feil.
Trinn 2: bekreft at grensesnittet tilhører appen din
Etter establish er ParcelFileDescriptor du mottar beviset ditt på eierskap, og det er verdt å sjekke før noe socket-arbeid. En deskriptor som er null eller allerede lukket betyr at grensesnittet aldri ble opprettet, selv om ingen unntak dukket opp. Sammenlign grensesnittnavnet fra builderen med det systemet rapporterer for den aktive VPN-en, ved hjelp av dumpsys på en testenhet. Hvis de er forskjellige, er tunnelen du bygde ikke tunnelen som bærer trafikk.
adb shell dumpsys connectivityDen samme uoverensstemmelsen forklarer et tilkoblet ikon med død trafikk. Systemet viser den andre appens tunnel, mens koden din holder en deskriptor for et grensesnitt den ikke eier.
Trinn 3: kall protect før connect
Når grensesnittet finnes, blir sockets appen din åpner rutet inn i tunnelen som standard. Den ytre transporten kan ikke følge den regelen, ellers ville den innkapslede pakken gå tilbake i grensesnittet, derfor VpnService protect markerer en socket for å omgå VPN-en og bruke det underliggende nettverket. Kallet returnerer en boolean, og å ignorere den er måten du ender opp med en socket som ser sunn ut og ikke bærer noe.
Kall protect før connect eller bind, aldri etter. Beskyttelsen må være på plass før socketen kobles til eller bindes, fordi en tilkoblet socket allerede har valgt ruten sin, og en senere protect flytter den ikke pålitelig. Hvis protect returnerer false, stopp og sjekk eierskapet på nytt: den feiler når grensesnittet ikke tilhører appen din, som er nøyaktig tilstanden en annen VPN etterlater. En false-retur er et signal om å gå tilbake til Trinn 1, ikke om å prøve samme socket igjen.
DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
return;
}
socket.connect(serverAddress, serverPort);Trinn 4: bind en prosess når en socket er vanskelig
Noen transporter er ikke én socket: et bibliotek kan åpne flere, eller du vil kanskje at hver tilkobling i en hjelpeprosess skal bruke samme utgang. ConnectivityManager.bindProcessToNetwork binder prosessen til et valgt nettverk, og den eldre setProcessDefaultNetwork gjorde det samme før den ble utdatert. Be om den underliggende tilkoblingen med NET_CAPABILITY_NOT_VPN slik at bindingen velger nettverket under tunnelen din og ikke selve tunnelen.
NetworkRequest request = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();Å binde en prosess er ikke en andre VPN. Det velger en utgang for den prosessen, noe som er nyttig for transporten din og ubrukelig for å kjøre to tunneler samtidig.
Fiks brukerens enhet, ikke kodeveien
Det finnes ingen API som lar to apper dele VPN-plassen, så rettelsen hører til enhetens tilstand. Tilby brukeren to konkrete veier: koble fra eller slett alltid på for den andre appen slik at prepare kan ta over, eller åpne den appens innstillinger per app og utelat appen din fra tunnelen dens. Unntaket holder filteret i gang for alt annet og lar appen din bruke den vanlige tilkoblingen, som ofte er resultatet brukeren faktisk ønsker.
Skriv begge veiene i støttedokumentasjonen din, fordi brukere sjelden vet hvilken app som eier VPN-en. Be dem sende navnet på den aktive VPN-en fra Innstillinger og resultatene av prepare, establish og protect fra loggene dine, og du kan si innen ett minutt hvilket trinn som feilet. Bekreft deretter med ekte trafikk: et navneoppslag, en TCP-tilkobling og én forespørsel gjennom tunnelen, ikke bare et tilkoblet ikon. En gyldig deskriptor og en grønn indikator beviser i beste fall eierskap, aldri at transporten overlevde rutingreglene.
Hvis alle fire kontrollene består og trafikken fortsatt dør, er eierskapet ikke lenger problemet ditt. Gå videre til ruting og servertilgjengelighet, og test de samme påloggingsdetaljene fra en enhet uten VPN-app installert.