A proteção do VpnService falha quando outra VPN já está ativa
O Android permite uma interface VPN por usuário. Execute as verificações prepare, establish, protect e de propriedade em ordem e depois escolha revogação ou exclusão por aplicativo.
Seu aplicativo chama VpnService.prepare, recebe null, constrói um túnel e o descritor parece válido, mas nada chega ao servidor. Ou prepare retorna um Intent, o usuário o aprova e establish continua falhando. Os dois resultados geralmente significam a mesma coisa: outro aplicativo já possui a interface VPN deste usuário, e suas chamadas protect estão marcando sockets que o túnel não pode usar. Percorra as verificações abaixo em ordem e pare na primeira que falhar, porque um passo posterior não pode reparar um anterior.
Verifique a vaga de VPN antes de tocar em protect
O Android dá a cada usuário exatamente uma interface VPN ativa. É uma regra da plataforma, não uma permissão que seu aplicativo possa solicitar, então dois clientes que querem ambos ser o túnel não podem coexistir.
Uma VPN sempre ativa configurada para outro aplicativo é o caso mais difícil, porque o sistema mantém esse aplicativo conectado e recusa uma tomada de controle. Um filtro de tráfego como o AdGuard, um cliente corporativo ou uma VPN de consumidor podem ocupar a vaga enquanto o usuário esqueceu que está instalado. Peça ao usuário que abra as Configurações e leia a entrada listada em VPN; o nome ali é o proprietário com quem você compete. Até que essa entrada seja desconectada ou a configuração sempre ativa seja apagada, seu VpnService não pode se tornar a interface.
Passo 1: leia o que prepare retorna
VpnService.prepare responde a uma única pergunta: este aplicativo pode se tornar a VPN? Ele retorna null quando o consentimento já existe, e um Intent quando o usuário ainda precisa aprová-lo, que você deve iniciar com startActivityForResult. Trate um retorno null como permissão para continuar, não como prova de que establish terá sucesso.
Intent intent = VpnService.prepare(context);
if (intent != null) {
startActivityForResult(intent, REQUEST_VPN);
}Se o caminho do Intent retorna e o usuário recusou, prepare continuará retornando um Intent na próxima tentativa. Registre o código de resultado em vez de tentar de novo em loop, porque um consentimento recusado é uma decisão do usuário, não um erro transitório.
Passo 2: confirme que a interface pertence ao seu aplicativo
Após establish, o ParcelFileDescriptor que você recebe é sua prova de propriedade, e vale a pena verificá-lo antes de qualquer trabalho com sockets. Um descritor null ou já fechado significa que a interface nunca foi criada, mesmo que nenhuma exceção tenha surgido. Compare o nome da interface do builder com o que o sistema relata para a VPN ativa, usando dumpsys em um dispositivo de teste. Se forem diferentes, o túnel que você construiu não é o túnel que transporta tráfego.
adb shell dumpsys connectivityA mesma divergência explica um ícone conectado com tráfego morto. O sistema mostra o túnel do outro aplicativo, enquanto seu código mantém um descritor de uma interface que não possui.
Passo 3: chame protect antes de connect
Assim que a interface existe, os sockets que seu aplicativo abre são roteados para o túnel por padrão. O transporte externo não pode seguir essa regra, senão o pacote encapsulado voltaria para a interface, então VpnService protect marca um socket para contornar a VPN e usar a rede subjacente. A chamada retorna um boolean, e ignorá-lo é como você termina com um socket que parece saudável e não transporta nada.
Chame protect antes de connect ou bind, nunca depois. A proteção precisa estar no lugar antes de o socket ser conectado ou vinculado, porque um socket conectado já escolheu sua rota e um protect posterior não o move de forma confiável. Se protect retornar false, pare e verifique a propriedade novamente: ele falha quando a interface não pertence ao seu aplicativo, que é exatamente o estado que outra VPN deixa. Um retorno false é um sinal para voltar ao Passo 1, não para tentar o mesmo socket de novo.
DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
return;
}
socket.connect(serverAddress, serverPort);Passo 4: vincule um processo quando um socket for incômodo
Alguns transportes não são um único socket: uma biblioteca pode abrir vários, ou você pode querer que cada conexão em um processo auxiliar use a mesma saída. ConnectivityManager.bindProcessToNetwork vincula o processo a uma rede escolhida, e o antigo setProcessDefaultNetwork fazia o mesmo antes de ficar obsoleto. Solicite a conexão subjacente com NET_CAPABILITY_NOT_VPN para que a vinculação selecione a rede sob o seu túnel e não o túnel em si.
NetworkRequest request = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();Vincular um processo não é uma segunda VPN. Escolhe uma saída para esse processo, o que é útil para o seu transporte e inútil para executar dois túneis ao mesmo tempo.
Corrija o dispositivo do usuário, não o caminho do código
Não há API que permita a dois aplicativos compartilhar a vaga de VPN, então a correção pertence ao estado do dispositivo. Ofereça ao usuário dois caminhos concretos: desconecte ou apague o sempre ativo do outro aplicativo para que seu prepare possa assumir, ou abra as configurações por aplicativo desse aplicativo e exclua seu aplicativo do túnel dele. A exclusão mantém o filtro funcionando para todo o resto e permite que seu aplicativo use a conexão comum, o que muitas vezes é o resultado que o usuário realmente quer.
Escreva os dois caminhos na sua documentação de suporte, porque os usuários raramente sabem qual aplicativo possui a VPN. Peça que enviem o nome da VPN ativa das Configurações e os resultados de prepare, establish e protect dos seus logs, e em um minuto você consegue dizer qual passo falhou. Depois verifique com tráfego real: uma consulta de nome, uma conexão TCP e uma requisição pelo túnel, não apenas um ícone conectado. Um descritor válido e um indicador verde provam a propriedade no máximo, nunca que o transporte sobreviveu às regras de roteamento.
Se as quatro verificações passarem e o tráfego ainda morrer, a propriedade não é mais o seu problema. Passe para o roteamento e a acessibilidade do servidor, e teste as mesmas credenciais a partir de um dispositivo sem nenhum aplicativo VPN instalado.