O VpnService de Android non ten ruta de exclusión, e ningunha cantidade de lectura da API producirá unha. Builder.addRoute() só engade destinos ao túnel, así que manter un segmento de LAN local significa que ese segmento nunca entra na lista de rutas. O que engades no seu lugar é o complemento do segmento, expresado como prefixos ordinarios. O traballo é aritmética, e falla nun puñado de lugares previsibles.

Percorre as comprobacións de abaixo en orde. Cada unha supón que a anterior se cumpriu, así que detente no primeiro fallo en lugar de parchear síntomas máis abaixo.

Comeza polo que o túnel reclama

Pasar 0.0.0.0/0 a addRoute() dille a Android que cada destino IPv4 pertence ao túnel. Por iso tantos clientes parecen incapaces de facer tunelización dividida: reclaman todo o espazo de enderezos e despois intentan restar del. Nada no construtor resta. Unha ruta máis específica para a rede que querías manter local fai o contrario do que a xente espera, porque gaña o prefixo coincidente máis longo e esa rede acaba dentro do túnel.

A lista de rutas é toda a política. Non hai veto por destino detrás dela, e a referencia de Builder non enumera ningún método que o proporcionaría.

Xera o complemento con aritmética de prefixos

O complemento dun prefixo excluído é o conxunto dos seus irmáns no camiño cara á raíz da árbore de enderezos. Para cada lonxitude desde 1 ata a lonxitude excluída, toma o bloque desa lonxitude que contén o teu prefixo, inverte o bit inmediatamente por baixo do prefixo e emite o resultado. Soa abstracto ata que o executas.

Excluír 10.0.0.0/8 produce oito rutas: 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. Ningunha contén 10.0.0.0/8, e xuntas cobren todo o demais.

Oito liñas xeran esa lista para calquera 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 rutas é igual á lonxitude do prefixo, e ese é o número que hai que vixiar. Unha soa exclusión /21 custa 21 rutas e segue sendo lexible. Dez exclusións poden custar uns centos, o que adoita significar que hai que cambiar o deseño da rede, non o xerador.

Comproba a familia de enderezos e o alcance do túnel

As rutas IPv4 non fan nada por IPv6. Se o túnel reclama un enderezo IPv6, un prefixo de LAN IPv6 necesita o seu propio complemento, xerado coa mesma aritmética sobre 128 bits. Unha causa habitual de que a exclusión funcione sobre o papel mentres o dispositivo local segue inalcanzable é esta: accedeuse ao host por IPv6, e só se recortara IPv4. Comproba ambas as familias antes de concluír que as matemáticas están mal.

O alcance é a outra metade da comprobación. Android rastrexa o enrutamento por rede e por aplicación, así que o mesmo prefixo pode ir polo túnel para unha aplicación e directo para outra segundo como se declarase a VPN.

Pon as regras de DNS e por aplicación onde corresponden

As exclusións de ruta non cambian o resolutor. Un nome de host local aínda pode ser respondido polo servidor DNS do túnel mentres o seu enderezo se enruta localmente, e o desaxuste vese como un nome que resolve a algo que a LAN non pode alcanzar. Define a política do resolutor a propósito, cun dominio de busca ou unha regra de DNS dividido. Non deas por feito que as rutas o decidiron por ti.

addDisallowedApplication() mantén unha aplicación fóra do túnel; non mantén un destino fóra para as aplicacións que quedan dentro. protect() permite que un socket que abres evite o túnel, e así un túnel evita capturar o seu propio transporte. Ningún dos dous substitúe o complemento.

Verifica cunha consulta, non coa túa propia lista

Unha vez levantado o túnel, pregunta ao dispositivo a onde vai realmente un enderezo en lugar de reler a lista que construíches. Nunha compilación que inclúe iproute2, adb shell ip route get 10.1.2.3 nomea a interface que escolleu o núcleo. Executa o mesmo comando para un enderezo público e confirma que nomea o túnel.

Dúas comprobacións máis pechan o círculo. Resolve primeiro un nome local, despois executa adb shell ip route get sobre o enderezo resolta e esixe unha interface que non sexa o túnel, porque un adb shell ping -c 1 host.lan correcto demostra alcanzabilidade e non o camiño. Repite a proba de familia de enderezos sobre IPv6 se o túnel reclama unha. Proba no nivel de API que distribúes, porque como se expoñen as redes VPN á táboa de enrutamento cambiou entre versións. Unha lista de rutas que parece correcta sobre o papel e unha consulta que escolle a interface equivocada son fallos distintos, e só o segundo chega aos usuarios.

Se a lista de exclusións segue crecendo, deixa de xerar rutas e renumera a rede remota ou estreita o túnel no seu lugar. O xerador é unha solución provisional para a API que falta, non unha ferramenta de deseño.

Artigos relacionados