Você adicionou um domínio à lista de permissões, a consulta chega ao seu aplicativo, o comparador retorna permitido, e a página continua sem carregar nunca. Nesse ponto a maioria das pessoas começa a reescrever o comparador e a acrescentar comparações com curingas. Pela minha experiência o comparador costuma estar correto, e a falha fica uma camada abaixo: na resposta DNS que você sintetizou, na rota que você instalou ou no socket que usou para encaminhar a consulta. A lista é a parte fácil; o caminho do pacote é onde as listas de permissões quebram.

O comparador raramente é o culpado

A primeira coisa a separar é bloqueado de quebrado. Um nome bloqueado deve falhar rápido e de forma visível, porque o navegador recebe uma resposta dizendo que o nome não existe e para. Um nome quebrado trava, tenta de novo por IPv6, cai para outro transporte e deixa você com um indicador girando. Se você vê um indicador girando, sua lista de permissões provavelmente está respondendo algo com que o cliente não consegue lidar.

O registro A vazio é o erro clássico. Retornar NOERROR com zero respostas parece uma resposta válida, então o resolvedor stub a aceita, o navegador tenta de novo, tenta AAAA, tenta outro resolvedor e desiste muito mais tarde. Um código de resposta 3, ou seja NXDOMAIN, com a seção de pergunta repetida e o ID de transação copiado, encerra a conversa imediatamente. Use-o para cada nome que você recusa deliberadamente.

IPv6 merece a mesma decisão e não um valor padrão. Se você responde NXDOMAIN para A e deixa AAAA passar, o cliente pode alcançar o nome bloqueado por IPv6 e o filtro vaza. Algumas implementações respondem consultas AAAA bloqueadas com um endereço de loopback para que a conexão falhe de imediato em vez de cair para uma alternativa. Escolha uma política, aplique-a aos dois tipos de registro e registre os dois.

Uma rota que captura tudo possui tudo

Um projeto só de DNS nunca adiciona addRoute("0.0.0.0", 0), então apenas consultas entram na interface TUN e todo o resto continua usando a rede subjacente. Essa é a forma segura, e também explica por que algumas listas de permissões parecem funcionar no Wi-Fi e falham na rede móvel.

No momento em que existe uma rota padrão, cada fluxo é seu. Um pacote TCP que chega à interface e não é encaminhado nem respondido fica na fila até o cliente estourar o tempo, e não há erro para registrar porque nada falhou. Vi alguém passar uma tarde em uma lista de permissões quebrada que na verdade era um encaminhador ausente para a porta 443. Registre o protocolo e o destino de cada pacote que você decidir descartar.

O socket do resolvedor precisa sair do túnel

O encaminhamento é onde está a próxima armadilha. Os resolvedores da operadora costumam viver em um endereço privado que o túnel não alcança, então a consulta que você encaminha não vai a lugar nenhum e o aplicativo relata tempo esgotado de leitura. Envie as consultas aprovadas para um resolvedor que você sabe ser acessível, e passe esse socket primeiro para VpnService.protect(). Um socket desprotegido devolve a consulta para o seu próprio túnel, onde ela chega ao aplicativo de novo e circula até o tempo limite disparar.

A falha parece idêntica a uma queda de rede, e por isso sobrevive tanto tempo nos relatórios de erros. Mantenha contadores de consultas enviadas e respostas recebidas: se o primeiro número sobe e o segundo fica em zero, você está olhando o socket, não a lista.

Tamanho e somas de verificação falham sem mensagem

Descates silenciosos também vêm do tamanho. Uma resposta DNS com um conjunto grande de respostas, ou um aperto de mão TCP com opções generosas, pode exceder uma MTU de caminho que você nunca definiu explicitamente; o fragmento desaparece adiante e o cliente ou tenta de novo menor ou para de tentar. Definir a MTU da interface em 1280 é uma escolha conservadora que sobrevive à maioria dos caminhos móveis, e ser explícito é melhor do que herdar um padrão que ninguém mediu.

Se você constrói os pacotes sozinho, a soma de verificação é o outro assassino silencioso. Uma soma de verificação UDP calculada sobre um pseudo-cabeçalho que omite o comprimento UDP ou o campo de protocolo produz um pacote que parece bem formado do seu lado e é descartado pelo receptor sem rastro. Compare seu pseudo-cabeçalho com a especificação campo por campo, e imprima a soma calculada para um fluxo conhecido enquanto depura.

O que um VpnService não fará por você

Dois limites valem a pena conhecer antes de projetar em torno deles. Nenhum aplicativo comum pode instalar um proxy de sistema ou arquivo PAC fora do Wi-Fi, e essa parte não mudou. O caminho por aplicativo mudou: antes da API 29, VpnService.Builder não podia carregar proxy nenhum, e desde a API 29 setHttpProxy(ProxyInfo.buildPacProxy(uri)) entrega à plataforma um script PAC para avaliar para a rede VPN. Avalie regras no seu próprio código quando mirar APIs mais antigas ou quando o modelo PAC não conseguir expressar a regra. UDP também não é grátis: navegadores enviam apenas TCP por um proxy SOCKS, e a maioria dos clientes SOCKS5 nunca implementa UDP ASSOCIATE, então um projeto baseado em proxy descarta ou vaza UDP enquanto TCP parece saudável.

Esse segundo limite é a razão pela qual um protocolo baseado em UDP pode funcionar numa rede comum e falhar no momento em que sua VPN conecta. Se espera-se que o túnel carregue UDP, o encaminhador precisa implementar o caminho de encaminhamento para isso, não apenas o caminho TCP com outro tipo de socket.

Verifique nesta ordem

Percorra a lista em ordem e pare no primeiro estágio que falhar, porque toda verificação posterior é ruído até a anterior se sustentar.

  1. Liste as rotas instaladas e confirme se pacotes que não são DNS entram na interface TUN.
  2. Registre o nome da consulta, a decisão e o código de resposta para um nome bloqueado e um aprovado.
  3. Confirme que o socket do resolvedor está protegido e que o resolvedor escolhido responde de dentro do túnel.
  4. Confirme a MTU da interface e a soma de verificação de uma resposta construída à mão.
  5. Repita a mesma página no Wi-Fi e na rede móvel, porque resolvedores da operadora e MTU do caminho diferem.

Se os cinco se sustentarem e a página ainda travar, o próximo suspeito é o próprio encaminhador: compare uma requisição capturada com uma resposta capturada e procure uma contagem de bytes faltando ou um socket meio fechado. A referência do VpnService.Builder cobre os contratos de rota, DNS e MTU, e protect() é uma chamada VpnService separada e não uma opção do builder; uma interface conectada significa apenas que o primeiro contrato foi cumprido.

Artigos relacionados