Din app kalder VpnService.prepare, får null tilbage, bygger en tunnel, og deskriptoren ser gyldig ud, men intet når serveren. Eller prepare returnerer et Intent, brugeren godkender det, og establish fejler stadig. Begge udfald betyder som regel det samme: en anden app ejer allerede VPN-grænsefladen for denne bruger, og dine protect-kald markerer sockets, som din tunnel ikke kan bruge. Gå gennem kontrollerne nedenfor i rækkefølge, og stop ved den første, der fejler, fordi et senere trin ikke kan reparere et tidligere.

Kontroller VPN-pladsen, før du rører protect

Android giver hver bruger præcis én aktiv VPN-grænseflade. Det er en platformregel, ikke en tilladelse, din app kan anmode om, så to klienter, der begge vil være tunnelen, kan ikke eksistere samtidig.

En altid aktiv VPN opsat for en anden app er det sværeste tilfælde, fordi systemet holder den app forbundet og afviser overtagelse. Et trafikfilter som AdGuard, en virksomhedsklient eller en forbruger-VPN kan holde pladsen, mens brugeren har glemt, at den er installeret. Bed brugeren om at åbne Indstillinger og læse posten under VPN; navnet der er ejeren, du konkurrerer med. Indtil den post er afbrudt, eller indstillingen altid aktiv er ryddet, kan din VpnService ikke blive grænsefladen.

Trin 1: læs, hvad prepare returnerer

VpnService.prepare besvarer ét spørgsmål: må denne app blive VPN’en? Den returnerer null, når samtykke allerede findes, og et Intent, når brugeren stadig skal godkende det, hvilket du skal starte med startActivityForResult. Behandl en null-retur som tilladelse til at fortsætte, ikke som bevis for, at establish lykkes.

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

Hvis Intent-stien vender tilbage, og brugeren har afvist, bliver prepare ved med at returnere et Intent ved næste forsøg. Log resultatkoden i stedet for at prøve igen i en løkke, fordi et afvist samtykke er en brugerbeslutning, ikke en forbigående fejl.

Trin 2: bekræft, at grænsefladen tilhører din app

Efter establish er den ParcelFileDescriptor, du modtager, dit bevis på ejerskab, og det er værd at kontrollere, før der sker noget med sockets. En deskriptor, der er null eller allerede lukket, betyder, at grænsefladen aldrig blev oprettet, selv om der ikke kom nogen undtagelse. Sammenlign grænsefladenavnet fra builderen med det, systemet rapporterer for den aktive VPN, ved hjælp af dumpsys på en testenhed. Hvis de er forskellige, er den tunnel, du byggede, ikke den tunnel, der bærer trafikken.

adb shell dumpsys connectivity

Den samme uoverensstemmelse forklarer et tilsluttet ikon med død trafik. Systemet viser den anden apps tunnel, mens din kode holder en deskriptor til en grænseflade, den ikke ejer.

Trin 3: kald protect før connect

Når grænsefladen findes, bliver sockets, som din app åbner, som standard routet ind i tunnelen. Den ydre transport kan ikke følge den regel, for så ville den indkapslede pakke løbe tilbage i grænsefladen, derfor VpnService protect markerer en socket for at omgå VPN’en og bruge det underliggende netværk. Kaldet returnerer en boolean, og at ignorere den er måden, hvorpå du ender med en socket, der ser sund ud og ikke bærer noget.

Kald protect før connect eller bind, aldrig efter. Beskyttelsen skal være på plads, før socketen forbindes eller bindes, fordi en forbundet socket allerede har valgt sin rute, og en senere protect flytter den ikke pålideligt. Hvis protect returnerer false, så stop og kontroller ejerskabet igen: den fejler, når grænsefladen ikke tilhører din app, hvilket er præcis den tilstand, en anden VPN efterlader. En false-retur er et signal om at gå tilbage til Trin 1, ikke om at prøve den samme socket igen.

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

Trin 4: bind en proces, når en socket er besværlig

Nogle transporter er ikke én socket: et bibliotek kan åbne flere, eller du vil måske have, at hver forbindelse i en hjælpeproces bruger den samme udgang. ConnectivityManager.bindProcessToNetwork binder processen til et valgt netværk, og den ældre setProcessDefaultNetwork gjorde det samme, før den blev forældet. Anmod om den underliggende forbindelse med NET_CAPABILITY_NOT_VPN, så bindingen vælger netværket under din tunnel i stedet for selve tunnelen.

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

At binde en proces er ikke en anden VPN. Det vælger en udgang for den proces, hvilket er nyttigt for din transport og ubrugeligt til at køre to tunneler på én gang.

Ret brugerens enhed, ikke kodevejen

Der findes ingen API, der lader to apps dele VPN-pladsen, så rettelsen hører til enhedens tilstand. Tilbyd brugeren to konkrete veje: afbryd eller ryd altid aktiv for den anden app, så din prepare kan overtage, eller åbn den apps indstillinger for enkelte apps og udeluk din app fra dens tunnel. Udelukkelsen holder filteret kørende for alt andet og lader din app bruge den almindelige forbindelse, hvilket ofte er det resultat, brugeren faktisk ønsker.

Skriv begge veje i din supportdokumentation, fordi brugere sjældent ved, hvilken app der ejer VPN’en. Bed dem sende navnet på den aktive VPN fra Indstillinger og resultaterne af prepare, establish og protect fra dine logfiler, så kan du på et minut sige, hvilket trin der fejlede. Bekræft derefter med rigtig trafik: et navneopslag, en TCP-forbindelse og én anmodning gennem tunnelen, ikke bare et tilsluttet ikon. En gyldig deskriptor og en grøn indikator beviser højst ejerskab, aldrig at transporten overlevede routingreglerne.

Hvis alle fire kontroller består, og trafikken stadig dør, er ejerskabet ikke dit problem længere. Gå videre til routing og serverens tilgængelighed, og test de samme legitimationsoplysninger fra en enhed uden en installeret VPN-app.

Relaterede artikler