Nada no VpnService intercepta tráfego sozinho. Ele pede ao sistema uma interface tun, entrega a essa interface um conjunto de endereços e rotas e, a partir desse momento, o núcleo copia cada pacote que corresponde a essas rotas para um descritor de arquivo que o aplicativo pode ler. O código do aplicativo nunca vê os pacotes que as rotas não selecionaram, então o escopo de um túnel é uma questão de tabela de roteamento antes de ser uma questão de programação. Por isso a mesma implementação do serviço pode engolir todo fluxo em um telefone e apenas parte em outro, e por isso adicionar uma rota às vezes conserta um túnel que parecia quebrado. O que você obtém é o que declarou, menos tudo o que o construtor recusou em silêncio.

As rotas decidem quais pacotes chegam

Builder.addRoute(prefix, prefixLength) instala um prefixo na tabela de roteamento da rede VPN. O sistema trata essa rede como padrão para os aplicativos no escopo, então suas entradas são consultadas antes da rota padrão da rede física e um prefixo correspondente basta para desviar um fluxo. Por isso uma rota para 0.0.0.0/0 leva IPv4 inteiro, incluindo a impressora, o alvo de cast e a página de administração do roteador.

Os clientes que querem essa cobertura sem apagar a entrada padrão física usam, em vez disso, o par 0.0.0.0/1 e 128.0.0.0/1. Duas rotas mais específicas vencem uma rota padrão, juntas cobrem todo o espaço de endereços, e a entrada padrão do próprio telefone fica na tabela para os sockets ainda ligados à rede física.

Endereços e entradas DNS vêm do mesmo construtor e respondem a outra pergunta. addAddress dá à interface um endereço com comprimento de prefixo, e addDnsServer instala um resolvedor que os aplicativos no escopo consultarão pela interface. As duas famílias podem ficar numa interface, então um conjunto de rotas só IPv4 ao lado de um endereço IPv6 é uma configuração que se lê corretamente e ainda assim vaza. Se você rotear uma família em que a interface não tem endereço, o núcleo não tem endereço de origem para esses pacotes e eles nunca aparecem no descritor.

O que establish() devolve

establish() devolve um ParcelFileDescriptor, ou null quando o aplicativo não está autorizado. Null é um estado de consentimento e não um erro de rede: VpnService.prepare() já deve ter devolvido null, ou o usuário deve ter aprovado a caixa de diálogo do sistema antes da chamada. O consentimento pode ser revogado enquanto o serviço roda, e a interface se fecha sob seus pés sem nenhum retorno de chamada para o seu laço de leitura.

O descritor não bloqueia a menos que você diga o contrário, então uma leitura retorna imediatamente sem dados em vez de estacionar a thread. Um laço escrito para um socket bloqueante queima um núcleo, e um laço que trata uma leitura vazia como fatal rasga o túnel num segundo ocioso. Builder.setBlocking(true) muda esse comportamento, e o preço é uma thread que espera dentro da leitura.

Argumentos de rota e endereço são validados como valores LinkAddress, e destinos multicast como 224.0.0.0/4 são rejeitados de imediato. Uma interface tun ponto a ponto também não tem domínio de broadcast, então a descoberta de multicast por ela exige outro projeto em vez de mais uma rota. A referência de VpnService.Builder é a lista a conferir antes de concluir que uma chamada foi ignorada.

O escopo por aplicativo estreita o conjunto, não o túnel

A partir da API 21 o construtor aceita addAllowedApplication e addDisallowedApplication, e as duas listas são mutuamente exclusivas dentro de um mesmo construtor. Uma lista de permitidos faz o túnel existir para esses pacotes enquanto todo o resto mantém a rede física; uma lista de bloqueados captura tudo exceto os pacotes nomeados, que é o que um cliente oferece como túnel dividido. Antes da API 21 não há mecanismo de plataforma nenhum, e o comportamento por aplicativo vinha de clientes de terceiros que filtravam por UID dentro do próprio encaminhador. A distinção importa ao depurar, porque um túnel por aplicativo com lista de um único pacote quase não gera tráfego e isso é comportamento correto, não rota quebrada.

Seja qual for o escopo, o socket que seu aplicativo abre para o servidor VPN não pode viajar pelo túnel que está construindo. VpnService.protect(socket) liga esse socket à rede subjacente para que seus pacotes pulem o dispositivo tun. Pule a chamada e o aperto de mão é engolido pela interface que deveria levantá-lo.

VpnService.setUnderlyingNetworks fecha o ciclo dizendo ao sistema qual rede física carrega o túnel. Esse valor determina como a VPN relata conectividade aos outros aplicativos, e um valor null ou desatualizado é uma causa comum de um túnel conectado que o resto do telefone trata como offline. Atualize-o quando a rede subjacente mudar, não só na inicialização.

Somas de verificação e MTU: onde uma rota correta ainda falha

Depois que os pacotes chegam, dois detalhes no nível do pacote decidem se algo funciona de ponta a ponta. O primeiro é o endereço de origem: com vários endereços de interface o núcleo pode emitir pacotes que carregam o primeiro, então um encaminhador que reescreve cabeçalhos precisa corrigir o endereço e a soma de verificação que o cobre.

O segundo é o escopo da soma de verificação. Quando o aplicativo constrói ele mesmo pacotes TCP ou UDP, a soma cobre um pseudo-cabeçalho com o endereço de origem, o endereço de destino, o número do protocolo e o comprimento de transporte, e omitir qualquer um deles produz pacotes que chegam e são descartados. Essa falha parece um tempo esgotado depois de uma resposta que nunca vem, o que manda a maioria das pessoas de volta às rotas. setMtu fixa a MTU da interface, e cada leitura devolve exatamente um pacote IP completo, então um buffer escrito para um fluxo de bytes fragmenta a sua análise, não o pacote.

Uma passada de verificação que separa as camadas

Para descobrir onde o escopo errou, teste as camadas em ordem. Pergunte ao dispositivo quais rotas a rede VPN realmente mantém, depois confira se um fluxo para um endereço conhecido aparece como leitura no descritor, depois confira se o pacote que seu encaminhador emite está bem formado. Um problema de rota não mostra nada no descritor, enquanto um problema de soma de verificação mostra um pacote que chega e desaparece.

Se nada chega ao descritor, o suspeito é o conjunto de rotas e não o leitor: confirme os prefixos, confirme que existe um endereço na mesma família e confirme que o aplicativo está dentro do escopo que você definiu. Se os pacotes chegam ao descritor mas nada volta, inspecione protect() e o valor da rede subjacente antes de tocar numa rota outra vez. Se o lado remoto relata pacotes malformados, a soma de verificação e a MTU são os lugares onde olhar. Uma sonda por camada economiza uma tarde de conjecturas.

Artigos relacionados