O VpnService do Android não tem rota de exclusão, e nenhuma quantidade de leitura da API produzirá uma. Builder.addRoute() só adiciona destinos ao túnel, então manter um segmento de LAN local significa que esse segmento nunca entra na lista de rotas. O que você adiciona, em vez disso, é o complemento do segmento, expresso como prefixos comuns. O trabalho é aritmética, e falha em alguns pontos previsíveis.

Percorra as verificações abaixo na ordem. Cada uma pressupõe que a anterior se manteve, então pare na primeira falha em vez de remendar sintomas mais adiante.

Comece pelo que o túnel reivindica

Passar 0.0.0.0/0 para addRoute() diz ao Android que cada destino IPv4 pertence ao túnel. É por isso que tantos clientes parecem incapazes de fazer tunelamento dividido: reivindicam todo o espaço de endereços e depois tentam subtrair dele. Nada no construtor subtrai. Uma rota mais específica para a rede que você queria manter local faz o contrário do que se espera, porque o prefixo mais longo que corresponde vence e essa rede acaba dentro do túnel.

A lista de rotas é toda a política. Não há veto por destino atrás dela, e a referência do Builder não lista nenhum método que o fornecesse.

Gere o complemento a partir da aritmética de prefixos

O complemento de um prefixo excluído é o conjunto dos seus irmãos no caminho até a raiz da árvore de endereços. Para cada comprimento de 1 até o comprimento excluído, pegue o bloco desse comprimento que contém o seu prefixo, inverta o bit imediatamente abaixo do prefixo e emita o resultado. Parece abstrato até você executá-lo.

Excluir 10.0.0.0/8 produz oito rotas: 128.0.0.0/1, 64.0.0.0/2, 32.0.0.0/3, 16.0.0.0/4, 0.0.0.0/5, 12.0.0.0/6, 8.0.0.0/7, 11.0.0.0/8. Nenhuma delas contém 10.0.0.0/8, e juntas cobrem todo o resto.

Oito linhas geram essa lista para qualquer prefixo:

import ipaddress
def complement(addr, length):
    base = int(ipaddress.IPv4Address(addr))
    out = []
    for k in range(1, length + 1):
        block = base & (0xFFFFFFFF << (32 - k))
        sibling = block ^ (1 << (32 - k))
        out.append(f"{ipaddress.IPv4Address(sibling)}/{k}")
    return out
print(complement("10.0.0.0", 8))

O número de rotas é igual ao comprimento do prefixo, e esse é o número a vigiar. Uma única exclusão /21 custa 21 rotas e continua legível. Dez exclusões podem custar algumas centenas, o que normalmente significa que o desenho da rede precisa mudar, não o gerador.

Verifique a família de endereços e o escopo do túnel

As rotas IPv4 não fazem nada pelo IPv6. Se o túnel reivindica um endereço IPv6, um prefixo de LAN IPv6 precisa do seu próprio complemento, gerado com a mesma aritmética sobre 128 bits. Uma causa comum de a exclusão funcionar no papel enquanto o dispositivo local continua inalcançável é esta: o host foi alcançado por IPv6, e apenas o IPv4 tinha sido recortado. Verifique as duas famílias antes de concluir que a matemática está errada.

O escopo é a outra metade da verificação. O Android rastreia o encaminhamento por rede e por aplicativo, então o mesmo prefixo pode passar pelo túnel para um aplicativo e ser direto para outro, dependendo de como a VPN foi declarada.

Coloque as regras de DNS e por aplicativo onde elas pertencem

As exclusões de rota não mudam o resolvedor. Um nome de host local ainda pode ser respondido pelo servidor DNS do túnel enquanto o endereço é encaminhado localmente, e a incompatibilidade aparece como um nome que resolve para algo que a LAN não alcança. Defina a política do resolvedor de propósito, com um domínio de busca ou uma regra de DNS dividido. Não presuma que as rotas decidiram isso por você.

addDisallowedApplication() mantém um aplicativo fora do túnel; não mantém um destino fora para os aplicativos que ficam dentro. protect() permite que um socket que você abre contorne o túnel, e é assim que um túnel evita capturar o próprio transporte. Nenhum dos dois substitui o complemento.

Verifique com uma consulta, não com a sua própria lista

Depois que o túnel estiver no ar, pergunte ao dispositivo para onde um endereço realmente vai em vez de reler a lista que você montou. Numa compilação que inclui o iproute2, adb shell ip route get 10.1.2.3 nomeia a interface que o kernel escolheu. Execute o mesmo comando para um endereço público e confirme que ele nomeia o túnel.

Mais duas verificações fecham o ciclo. Resolva primeiro um nome local, depois execute adb shell ip route get no endereço resolvido e exija uma interface que não seja o túnel, porque um adb shell ping -c 1 host.lan bem-sucedido prova alcance e não o caminho. Repita o teste de família de endereços em IPv6 se o túnel reivindicar uma. Teste no nível de API que você distribui, porque a forma como as redes VPN são expostas à tabela de encaminhamento mudou entre versões. Uma lista de rotas que parece correta no papel e uma consulta que escolhe a interface errada são bugs diferentes, e só o segundo chega aos usuários.

Se a lista de exclusões continuar crescendo, pare de gerar rotas e renumere a rede remota ou estreite o túnel. O gerador é uma gambiarra para a API ausente, não uma ferramenta de desenho.

Artigos relacionados