VPN

O Android oferece APIs para que os desenvolvedores criem soluções de rede privada virtual (VPN). Depois de ler este guia, você saberá como desenvolver e testar seu próprio cliente VPN para dispositivos Android.

Visão geral

As VPNs permitem que dispositivos fora de uma rede acessem com segurança.

O Android inclui um cliente VPN integrado (PPTP e L2TP/IPSec), que às vezes é chamado de VPN legada. O Android 4.0 (API de nível 14) introduziu APIs para que os desenvolvedores de apps pudessem fornecer as próprias soluções de VPN. Você empacota sua solução de VPN em um app que as pessoas instalam no dispositivo. Normalmente, os desenvolvedores criam um app de VPN por um dos seguintes motivos:

  • Oferecer protocolos VPN incompatíveis com o cliente integrado.
  • Ajudar as pessoas a se conectarem a um serviço de VPN sem configuração complexa.

O restante deste guia explica como desenvolver apps de VPN (incluindo VPN sempre ativada e por app) e não abrange o cliente VPN integrado.

Experiência do usuário

O Android oferece uma interface do usuário para ajudar a configurar, iniciar e interromper sua solução de VPN. A IU do sistema também avisa o usuário do dispositivo de que há uma conexão VPN ativa. O Android mostra estes componentes de IU para conexões VPN:

  • Antes de um app de VPN ser ativado pela primeira vez, o sistema exibe uma caixa de diálogo de solicitação de conexão. A caixa de diálogo solicita que a pessoa que usa o dispositivo confirme que confia na VPN e aceite a solicitação.
  • A tela de configurações da VPN (Configurações > Rede e Internet > VPN) mostra os apps de VPN em que uma pessoa aceitou solicitações de conexão. Há um botão para configurar opções do sistema ou esquecer a VPN.
  • A bandeja "Configurações rápidas" mostra um painel de informações quando uma conexão está ativa. Tocar no rótulo exibe uma caixa de diálogo com mais informações e um link para as configurações.
  • A barra de status inclui o ícone de VPN (chave) para indicar uma conexão ativa.

Seu app também precisa fornecer uma interface para que o usuário possa configurar as opções do seu serviço. Por exemplo, sua solução pode precisar capturar as configurações de autenticação da conta. Os apps devem exibir a seguinte IU:

  • Controles para iniciar e interromper manualmente uma conexão. A VPN sempre ativa pode se conectar quando necessário, mas permite que as pessoas configurem a conexão na primeira vez que usarem sua VPN.
  • Uma notificação não dispensável quando o serviço está ativo. A notificação pode mostrar o status da conexão ou fornecer mais informações, como estatísticas da rede. O toque na notificação coloca o app em primeiro plano. Remova a notificação depois que o serviço ficar inativo.

Serviço de VPN

O app conecta a rede do sistema de um usuário (ou um perfil de trabalho) a um gateway de VPN. Cada usuário (ou perfil de trabalho) pode executar um app de VPN diferente. Você cria um serviço de VPN que o sistema usa para iniciar e interromper a VPN e rastreia o status da conexão. Seu serviço de VPN é herdado de VpnService.

O serviço também atua como seu contêiner para as conexões de gateway da VPN e as interfaces de dispositivo local. A instância de serviço chama os métodos VpnService.Builder para estabelecer uma nova interface local.

Figura 1. Como o VpnService conecta a rede do Android ao gateway da VPN
Diagrama de arquitetura de blocos mostrando como o VpnService cria uma interface TUN
         local na rede do sistema.

O app transfere os seguintes dados para conectar o dispositivo ao gateway da VPN:

  • Lê pacotes de IP de saída do descritor de arquivos da interface local, os criptografa e os envia para o gateway da VPN.
  • Grava os pacotes de entrada (recebidos e descriptografados do gateway da VPN) no descritor de arquivos da interface local.

Há apenas um serviço ativo por usuário ou perfil. Iniciar um novo serviço interrompe automaticamente um serviço atual.

Adicionar um serviço

Para adicionar um serviço de VPN ao app, crie um serviço Android herdado de VpnService. Declare o serviço de VPN no arquivo de manifesto do app com as seguintes adições:

  • Proteja o serviço com a permissão BIND_VPN_SERVICE para que somente o sistema possa se vincular ao serviço.
  • Anuncie o serviço com o filtro de intent "android.net.VpnService" para que o sistema possa encontrar seu serviço.

Este exemplo mostra como declarar o serviço no arquivo de manifesto do app:

<service android:name=".MyVpnService"
         android:permission="android.permission.BIND_VPN_SERVICE">
     <intent-filter>
         <action android:name="android.net.VpnService"/>
     </intent-filter>
</service>

Agora que o app declara o serviço, o sistema pode iniciar e interromper automaticamente o serviço de VPN do app quando necessário. Por exemplo, o sistema controla seu serviço ao executar a VPN sempre ativa.

Preparar um serviço

Para preparar o app para se tornar o serviço de VPN atual do usuário, chame VpnService.prepare(). Se o usuário do dispositivo ainda não tiver concedido permissão ao app, o método retornará uma intent de atividade. Use esse intent para iniciar uma atividade do sistema que solicite a permissão. O sistema mostra uma caixa de diálogo parecida com outras caixas de diálogo de permissões, como o acesso à câmera ou aos contatos. Se o app já estiver preparado, o método retornará null.

Apenas um app pode ser o atual serviço de VPN preparado. Sempre chame VpnService.prepare(), porque uma pessoa pode ter definido um app diferente como o serviço de VPN desde que seu app chamou o método pela última vez. Para saber mais, consulte a seção Ciclo de vida do serviço.

Conectar um serviço

Quando o serviço estiver em execução, você poderá estabelecer uma nova interface local conectada a um gateway de VPN. Para solicitar permissão e se conectar ao seu serviço ao gateway da VPN, siga as etapas na seguinte ordem:

  1. Chame VpnService.prepare() para pedir permissão (quando necessário).
  2. Chame VpnService.protect() para manter o soquete do túnel do seu app fora da VPN do sistema e evitar uma conexão circular.
  3. Chame DatagramSocket.connect() para conectar o soquete do túnel do app ao gateway da VPN.
  4. Chame os métodos VpnService.Builder para configurar uma nova interface TUN local no dispositivo para o tráfego da VPN.
  5. Chame VpnService.Builder.establish() para que o sistema estabeleça a interface TUN local e comece a rotear o tráfego pela interface.

Um gateway da VPN normalmente sugere configurações para a interface TUN local durante o handshake. O app chama métodos VpnService.Builder para configurar um serviço, conforme mostrado no exemplo a seguir:

Kotlin

// Configure a new interface from our VpnService instance. This must be done
// from inside a VpnService.
val builder = Builder()

// Create a local TUN interface using predetermined addresses. In your app,
// you typically use values returned from the VPN gateway during handshaking.
val localTunnel = builder
        .addAddress("192.168.2.2", 24)
        .addRoute("0.0.0.0", 0)
        .addDnsServer("192.168.1.1")
        .establish()

Java

// Configure a new interface from our VpnService instance. This must be done
// from inside a VpnService.
VpnService.Builder builder = new VpnService.Builder();

// Create a local TUN interface using predetermined addresses. In your app,
// you typically use values returned from the VPN gateway during handshaking.
ParcelFileDescriptor localTunnel = builder
    .addAddress("192.168.2.2", 24)
    .addRoute("0.0.0.0", 0)
    .addDnsServer("192.168.1.1")
    .establish();

O exemplo na seção VPN por app mostra uma configuração de IPv6 com mais opções. É necessário adicionar os seguintes valores de VpnService.Builder antes de estabelecer uma nova interface:

addAddress()
Adicione pelo menos um endereço IPv4 ou IPv6 com uma máscara de sub-rede atribuída pelo sistema como o endereço da interface TUN local. Normalmente, o app recebe os endereços IP e as máscaras de sub-rede de um gateway da VPN durante o handshake.
addRoute()
Adicione pelo menos uma rota se quiser que o sistema envie tráfego por meio da interface de VPN. As rotas são filtradas por endereços de destino. Para aceitar todo o tráfego, defina uma rota aberta, como 0.0.0.0/0 ou ::/0.

O método establish() retorna uma instância ParcelFileDescriptor que o app usa para ler e gravar pacotes no buffer da interface e a partir dele. O método establish() retornará null se o app não estiver preparado ou se alguém revogar a permissão.

Ciclo de vida do serviço

Seu app precisa rastrear o status da VPN selecionada do sistema e todas as conexões ativas. Atualize a interface do usuário (IU) do app para manter a pessoa que está usando o dispositivo ciente de quaisquer mudanças.

Iniciar um serviço

O serviço de VPN pode ser iniciado das seguintes maneiras:

  • Seu app inicia o serviço, normalmente porque o usuário tocou em um botão de conexão.
  • O sistema inicia o serviço porque a VPN sempre ativa está ligada.

O app inicia o serviço de VPN transmitindo uma intent para startService(). Para saber mais, leia Como iniciar um serviço.

O sistema inicia o serviço em segundo plano chamando onStartCommand(). No entanto, o Android coloca restrições em apps em segundo plano na versão 8.0 (nível 26 da API) ou mais recente. Se você oferecer suporte a esses níveis de API, vai precisar fazer a transição do serviço para o primeiro plano chamando Service.startForeground(). Para saber mais, leia Como executar um serviço em primeiro plano.

Interromper um serviço

O usuário do dispositivo pode interromper o serviço usando a IU do seu app. Interrompa o serviço em vez de apenas encerrar a conexão. O sistema também interrompe uma conexão ativa quando o usuário do dispositivo faz o seguinte na tela da VPN do app Configurações:

  • Desconecta ou esquece o app de VPN.
  • Troca a VPN sempre ativa por uma conexão ativa.

O sistema chama o método onRevoke() do seu serviço, mas essa chamada pode não acontecer na linha de execução principal. Quando o sistema chama esse método, uma interface de rede alternativa já está roteando o tráfego. Você pode descartar os seguintes recursos com segurança:

VPN sempre ativa

O Android pode iniciar um serviço de VPN quando o dispositivo é inicializado e mantê-lo em execução enquanto o dispositivo estiver ligado. Esse recurso é chamado de VPN sempre ativa e está disponível no Android 7.0 (API de nível 24) ou versões mais recentes. Embora o Android mantenha o ciclo de vida do serviço, seu serviço de VPN é responsável pela conexão do gateway da VPN. A VPN sempre ativa também pode bloquear conexões que não usam a VPN.

Experiência do usuário

No Android 8.0 ou versões mais recentes, o sistema mostra as caixas de diálogo abaixo para informar ao usuário do dispositivo que a VPN sempre ativa está ativa:

  • Quando as conexões VPN sempre ativadas são desconectadas ou não conseguem se conectar, as pessoas veem uma notificação não dispensável. Tocar na notificação mostra uma caixa de diálogo com mais detalhes. A notificação desaparece quando a VPN é reconectada ou alguém desativa a opção de VPN sempre ativa.
  • A VPN sempre ativada permite que o usuário do dispositivo bloqueie qualquer conexão de rede que não use a VPN. Quando você ativa essa opção, o app Configurações avisa às pessoas que elas não têm uma conexão de Internet antes da conexão da VPN. O app Configurações solicita que o usuário do dispositivo continue ou cancele.

Como o sistema (e não uma pessoa) inicia e interrompe uma conexão sempre ativada, é necessário adaptar o comportamento e a interface do usuário do app:

  1. Desative qualquer interface que desconecte a conexão, porque ela é controlada pelo sistema e pelo app Configurações.
  2. Salve as configurações entre cada inicialização do app e configure uma conexão com as configurações mais recentes. Como o sistema inicia seu app sob demanda, é possível que o usuário do dispositivo nem sempre queira configurar uma conexão.

Você também pode usar configurações gerenciadas para configurar uma conexão. As configurações gerenciadas ajudam o administrador de TI a configurar sua VPN remotamente.

Detectar a opção "sempre ativa"

O Android não inclui APIs para confirmar se o sistema iniciou seu serviço de VPN. No entanto, quando seu app sinalizar qualquer instância de serviço iniciada, presuma que o sistema iniciou serviços não sinalizados para a VPN sempre ativa. Veja um exemplo:

  1. Crie uma instância Intent para iniciar o serviço de VPN.
  2. Sinalize o serviço de VPN colocando um extra no intent.
  3. No método onStartCommand() do serviço, procure a sinalização nos extras do argumento intent.

Conexões bloqueadas

Um usuário do dispositivo (ou um administrador de TI) pode forçar todo o tráfego a usar a VPN. O sistema bloqueará qualquer tráfego de rede que não use a VPN. Os usuários do dispositivo podem encontrar a chave Bloquear conexões sem VPN no painel de opções da VPN, em "Configurações".

Desativar a opção "sempre ativa"

Caso seu app não ofereça suporte à VPN sempre ativa, desative essa opção (no Android 8.1 ou versões mais recentes) configurando os metadados de serviço SERVICE_META_DATA_SUPPORTS_ALWAYS_ON como false. O exemplo de manifesto do app abaixo mostra como adicionar o elemento de metadados:

<service android:name=".MyVpnService"
         android:permission="android.permission.BIND_VPN_SERVICE">
     <intent-filter>
         <action android:name="android.net.VpnService"/>
     </intent-filter>
     <meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
             android:value=false/>
</service>

Quando o app desativa a VPN sempre ativa, o sistema desativa os controles de opções da interface em "Configurações".

VPN por app

Os apps de VPN podem filtrar quais apps instalados têm permissão para enviar tráfego pela conexão VPN. É possível criar uma lista de permissões ou de proibições, mas não ambas. Se você não criar listas permitidas ou não permitidas, o sistema vai enviar todo o tráfego de rede pela VPN.

O app de VPN precisa definir as listas antes que a conexão seja estabelecida. Se você precisar alterar as listas, estabeleça uma nova conexão VPN. Um app precisa ser instalado no dispositivo quando você o adiciona a uma lista.

Kotlin

// The apps that will have access to the VPN.
val appPackages = arrayOf(
        "com.android.chrome",
        "com.google.android.youtube",
        "com.example.a.missing.app")

// Loop through the app packages in the array and confirm that the app is
// installed before adding the app to the allowed list.
val builder = Builder()
for (appPackage in appPackages) {
    try {
        packageManager.getPackageInfo(appPackage, 0)
        builder.addAllowedApplication(appPackage)
    } catch (e: PackageManager.NameNotFoundException) {
        // The app isn't installed.
    }
}

// Complete the VPN interface config.
val localTunnel = builder
        .addAddress("2001:db8::1", 64)
        .addRoute("::", 0)
        .establish()

Java

// The apps that will have access to the VPN.
String[] appPackages = {
    "com.android.chrome",
    "com.google.android.youtube",
    "com.example.a.missing.app"};

// Loop through the app packages in the array and confirm that the app is
// installed before adding the app to the allowed list.
VpnService.Builder builder = new VpnService.Builder();
PackageManager packageManager = getPackageManager();
for (String appPackage: appPackages) {
  try {
    packageManager.getPackageInfo(appPackage, 0);
    builder.addAllowedApplication(appPackage);
  } catch (PackageManager.NameNotFoundException e) {
    // The app isn't installed.
  }
}

// Complete the VPN interface config.
ParcelFileDescriptor localTunnel = builder
    .addAddress("2001:db8::1", 64)
    .addRoute("::", 0)
    .establish();

Apps permitidos

Para adicionar um app à lista de permissões, chame VpnService.Builder.addAllowedApplication(). Se a lista incluir um ou mais apps, somente aqueles que estiverem na lista vão usar a VPN. Todos os outros apps que não estiverem na lista vão usar as redes do sistema como se a VPN não estivesse em execução. Quando a lista de permissões está vazia, todos os apps usam a VPN.

Apps não permitidos

Para adicionar um app à lista de permissões, chame VpnService.Builder.addDisallowedApplication(). Os apps não permitidos usam a rede do sistema como se a VPN não estivesse em execução. Todos os outros usarão a VPN.

Ignorar a VPN

A VPN pode permitir que os apps a ignorem e selecionem a própria rede. Para ignorar a VPN, chame VpnService.Builder.allowBypass() ao estabelecer uma interface da VPN. Não é possível alterar esse valor depois de iniciar o serviço de VPN. Se um app não vincular o processo ou um soquete a uma rede específica, o tráfego de rede do app continuará pela VPN.

Os apps que se vinculam a uma rede específica não têm conexão quando alguém bloqueia o tráfego que não passa pela VPN. Para enviar tráfego por uma rede específica, os apps chamam métodos, como ConnectivityManager.bindProcessToNetwork() ou Network.bindSocket(), antes de conectar o soquete.

Exemplo de código

O Android Open Source Project inclui uma amostra de app denominada ToyVPN. Esse app mostra como configurar e conectar um serviço de VPN.