El VpnService d'Android no té cap ruta d'exclusió, i cap quantitat de lectura de l'API no en produirà una. Builder.addRoute() només afegeix destinacions al túnel, així que mantenir un segment de LAN local vol dir que aquell segment no entra mai a la llista de rutes. El que afegiu en lloc d'això és el complement del segment, expressat com a prefixos ordinaris. La feina és aritmètica, i falla en uns quants llocs previsibles.

Feu les comprovacions de sota en ordre. Cada una assumeix que l'anterior s'ha complert, així que pareu a la primera fallada en lloc d'arreglar símptomes més avall.

Comenceu per allò que el túnel reclama

Passar 0.0.0.0/0 a addRoute() diu a Android que cada destinació IPv4 pertany al túnel. És per això que tants clients semblen incapaços de fer tunelització dividida: reclamen tot l'espai d'adreces i després intenten restar-ne. Res al constructor no resta. Una ruta més específica per a la xarxa que volíeu mantenir local fa el contrari del que la gent espera, perquè guanya el prefix coincident més llarg i aquella xarxa acaba dins del túnel.

La llista de rutes és tota la política. No hi ha cap veto per destinació darrere seu, i la referència de Builder no enumera cap mètode que el proporcioni.

Genereu el complement amb aritmètica de prefixos

El complement d'un prefix exclòs és el conjunt dels seus germans al camí fins a l'arrel de l'arbre d'adreces. Per a cada longitud des d'1 fins a la longitud exclosa, preneu el bloc d'aquella longitud que conté el vostre prefix, canvieu el bit immediatament per sota del prefix i emeteu el resultat. Sona abstracte fins que el executeu.

Excloure 10.0.0.0/8 produeix vuit rutes: 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. Cap no conté 10.0.0.0/8, i juntes cobreixen tota la resta.

Vuit línies generen aquella llista per a qualsevol prefix:

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))

El nombre de rutes és igual a la longitud del prefix, i aquest és el número a vigilar. Una sola exclusió /21 costa 21 rutes i es manté llegible. Deu exclusions poden costar uns quants centenars, cosa que normalment vol dir que cal canviar el disseny de la xarxa, no el generador.

Comproveu la família d'adreces i l'abast del túnel

Les rutes IPv4 no fan res per a IPv6. Si el túnel reclama una adreça IPv6, un prefix de LAN IPv6 necessita el seu propi complement, generat amb la mateixa aritmètica sobre 128 bits. Una causa habitual que l'exclusió funcioni sobre paper mentre el dispositiu local continua inabastable és aquesta: s'arribava a l'amfitrió per IPv6, i només s'havia retallat IPv4. Comproveu les dues famílies abans de concloure que les matemàtiques són incorrectes.

L'abast és l'altra meitat de la comprovació. Android segueix l'encaminament per xarxa i per aplicació, així que el mateix prefix pot anar pel túnel per a una aplicació i directe per a una altra segons com s'hagi declarat la VPN.

Poseu les regles de DNS i per aplicació on toca

Les exclusions de ruta no canvien el resolutor. Un nom d'amfitrió local encara pot ser respost pel servidor DNS del túnel mentre la seva adreça s'encamina localment, i el desajust es veu com un nom que resol a una cosa que la LAN no pot abastar. Definiu la política del resolutor deliberadament, amb un domini de cerca o una regla de DNS dividit. No assumiu que les rutes ho han decidit per vosaltres.

addDisallowedApplication() manté una aplicació fora del túnel; no manté una destinació fora per a les aplicacions que hi queden dins. protect() permet que un sòcol que obriu evasioni el túnel, i així un túnel evita capturar el seu propi transport. Cap de les dues substitueix el complement.

Verifiqueu amb una consulta, no amb la vostra llista

Un cop el túnel és amunt, pregunteu al dispositiu on va realment una adreça en lloc de rellegir la llista que heu construït. En una compilació que inclou iproute2, adb shell ip route get 10.1.2.3 anomena la interfície que el nucli ha seleccionat. Executeu la mateixa ordre per a una adreça pública i confirmeu que anomena el túnel.

Dues comprovacions més tanquen el cercle. Resoleu primer un nom local, després executeu adb shell ip route get sobre l'adreça resolta i exigiu una interfície que no sigui el túnel, perquè un adb shell ping -c 1 host.lan correcte demostra abastabilitat i no el camí. Repetiu la prova de família d'adreces sobre IPv6 si el túnel en reclama una. Proveu al nivell d'API que distribuïu, perquè com s'exposen les xarxes VPN a la taula d'encaminament ha canviat entre versions. Una llista de rutes que sembla correcta sobre paper i una consulta que tria la interfície equivocada són errors diferents, i només el segon arriba als usuaris.

Si la llista d'exclusions continua creixent, pareu de generar rutes i renumereu la xarxa remota o estretiu el túnel en lloc d'això. El generador és una solució provisional per a l'API que falta, no una eina de disseny.

Articles relacionats