La vostra aplicació crida VpnService.prepare, obté null, construeix un túnel i el descriptor sembla vàlid, però no arriba res al servidor. O prepare retorna un Intent, l’usuari l’aprova i establish continua fallant. Tots dos resultats solen significar el mateix: una altra aplicació ja posseeix la interfície VPN per a aquest usuari, i les vostres crides protect estan marcant sockets que el túnel no pot utilitzar. Recorreu les comprovacions següents en ordre i atureu-vos a la primera que falli, perquè un pas posterior no pot reparar un d’anterior.

Comproveu la plaça VPN abans de tocar protect

Android dona a cada usuari exactament una interfície VPN activa. És una regla de la plataforma, no un permís que la vostra aplicació pugui demanar, així que dos clients que vulguin ser el túnel no poden coexistir.

Un VPN sempre actiu configurat per a una altra aplicació és el cas més difícil, perquè el sistema manté aquella aplicació connectada i rebutja una presa de control. Un filtre de trànsit com AdGuard, un client corporatiu o un VPN de consum poden ocupar la plaça mentre l’usuari ha oblidat que està instal·lat. Demaneu a l’usuari que obri Configuració i llegeixi l’entrada que apareix a VPN; el nom que hi ha és el propietari amb qui competiu. Fins que aquella entrada no es desconnecti o no s’esborri la configuració de sempre actiu, el vostre VpnService no pot esdevenir la interfície.

Pas 1: llegiu què retorna prepare

VpnService.prepare respon una sola pregunta: pot aquesta aplicació esdevenir el VPN? Retorna null quan ja existeix consentiment, i un Intent quan l’usuari encara l’ha d’aprovar, que heu de llançar amb startActivityForResult. Tracteu un retorn null com a permís per continuar, no com a prova que establish tindrà èxit.

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

Si el camí de l’Intent torna i l’usuari ha refusat, prepare continuarà retornant un Intent en el següent intent. Registreu el codi de resultat en lloc de reintentar en bucle, perquè un consentiment refusat és una decisió de l’usuari, no un error transitori.

Pas 2: confirmeu que la interfície pertany a la vostra aplicació

Després d’establish, el ParcelFileDescriptor que rebeu és la prova de propietat, i val la pena comprovar-lo abans de qualsevol treball amb sockets. Un descriptor null o ja tancat significa que la interfície no es va crear mai, encara que no hagi aparegut cap excepció. Compareu el nom de la interfície del builder amb el que el sistema informa per al VPN actiu, fent servir dumpsys en un dispositiu de prova. Si difereixen, el túnel que heu construït no és el túnel que transporta trànsit.

adb shell dumpsys connectivity

La mateixa discrepància explica una icona connectada amb trànsit mort. El sistema mostra el túnel de l’altra aplicació, mentre el vostre codi té un descriptor d’una interfície que no posseeix.

Pas 3: crideu protect abans de connect

Un cop existeix la interfície, els sockets que obre la vostra aplicació s’encaminen al túnel per defecte. El transport exterior no pot seguir aquesta regla o el paquet encapsulat tornaria a la interfície, així que VpnService protect marca un socket per saltar-se el VPN i fer servir la xarxa subjacent. La crida retorna un booleà, i ignorar-lo és com acabes amb un socket que sembla sa i no transporta res.

Crideu protect abans de connect o bind, mai després. La protecció ha d’estar en vigor abans que el socket es connecti o s’enllaci, perquè un socket connectat ja ha triat la seva ruta i un protect posterior no la mou de manera fiable. Si protect retorna false, atureu-vos i torneu a comprovar la propietat: falla quan la interfície no pertany a la vostra aplicació, que és exactament l’estat que deixa un altre VPN. Un retorn false és un senyal per tornar al Pas 1, no per reintentar el mateix socket.

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

Pas 4: enllaceu un procés quan un socket és incòmode

Alguns transports no són un sol socket: una biblioteca pot obrir-ne diversos, o potser voleu que cada connexió d’un procés auxiliar faci servir la mateixa sortida. ConnectivityManager.bindProcessToNetwork enllaça el procés a una xarxa triada, i l’antic setProcessDefaultNetwork feia la mateixa feina abans de quedar obsolet. Demaneu la connexió subjacent amb NET_CAPABILITY_NOT_VPN perquè l’enllaç seleccioni la xarxa sota el vostre túnel i no el túnel mateix.

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

Enllaçar un procés no és un segon VPN. Tria una sortida per a aquell procés, cosa útil per al vostre transport i inútil per executar dos túnels alhora.

Repareu el dispositiu de l’usuari, no el camí del codi

No hi ha cap API que permeti a dues aplicacions compartir la plaça VPN, així que la solució és a l’estat del dispositiu. Oferiu a l’usuari dos camins concrets: desconnecteu o esborreu el sempre actiu de l’altra aplicació perquè el vostre prepare pugui prendre el relleu, o obriu la configuració per aplicació d’aquella aplicació i excloeu la vostra aplicació del seu túnel. L’exclusió manté el filtre en marxa per a tota la resta i permet que la vostra aplicació faci servir la connexió ordinària, que sovint és el resultat que l’usuari vol de debò.

Escriviu tots dos camins a la vostra documentació de suport, perquè els usuaris rares vegades saben quina aplicació posseeix el VPN. Demaneu-los que enviïn el nom del VPN actiu de Configuració i els resultats de prepare, establish i protect dels vostres registres, i en un minut podeu dir quin pas ha fallat. Després verifiqueu amb trànsit real: una consulta de nom, una connexió TCP i una petició a través del túnel, no només una icona connectada. Un descriptor vàlid i un indicador verd proven la propietat com a màxim, mai que el transport hagi sobreviscut a les regles d’encaminament.

Si les quatre comprovacions passen i el trànsit encara mor, la propietat ja no és el vostre problema. Passeu a l’encaminament i a l’abast del servidor, i proveu les mateixes credencials des d’un dispositiu sense cap aplicació VPN instal·lada.

Articles relacionats