Cum să excluzi o subrețea dintr-un tunel VPN Android
VpnService nu poate exclude o rută, așa că adaugi complementul: opt prefixe generate pentru un /8, plus verificările IPv6, DNS și de căutare care prind restul.
VpnService din Android nu are o rută de excludere, iar nicio cantitate de citire a API-ului nu va produce una. Builder.addRoute() adaugă mereu doar destinații în tunel, așa că păstrarea unui segment LAN local înseamnă că segmentul nu intră niciodată în lista de rute. Ce adaugi în schimb este complementul segmentului, exprimat ca prefixe obișnuite. Munca este aritmetică, și eșuează în câteva locuri previzibile.
Parcurge verificările de mai jos în ordine. Fiecare presupune că precedenta a rezistat, așa că oprește-te la primul eșec în loc să peticești simptome mai jos.
Începe cu ceea ce pretinde tunelul
Transmiterea 0.0.0.0/0 către addRoute() spune Androidului că fiecare destinație IPv4 aparține tunelului. De aceea atât de mulți clienți par incapabili de tunelare divizată: revendică întregul spațiu de adrese și apoi încearcă să scadă din acesta. Nimic din constructor nu scade. O rută mai specifică pentru rețeaua pe care voiai să o păstrezi local face opusul a ceea ce se așteaptă oamenii, pentru că prefixul potrivit cel mai lung câștigă și acea rețea ajunge în interiorul tunelului.
Lista de rute este întreaga politică. Nu există veto pe destinație în spatele ei, iar referința Builder nu enumeră nicio metodă care ar oferi una.
Generează complementul din aritmetica prefixelor
Complementul unui prefix exclus este mulțimea fraților săi pe calea spre rădăcina arborelui de adrese. Pentru fiecare lungime de la 1 până la lungimea exclusă, ia blocul de acea lungime care conține prefixul tău, inversează bitul imediat sub prefix și emite rezultatul. Sună abstract până îl rulezi.
Excluderea 10.0.0.0/8 produce opt rute: 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. Niciuna nu conține 10.0.0.0/8, iar împreună acoperă tot restul.
Opt linii generează acea listă pentru orice 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))
Numărul de rute este egal cu lungimea prefixului, și acesta este numărul de urmărit. O singură excludere /21 costă 21 de rute și rămâne lizibilă. Zece excluderi pot costa câteva sute, ceea ce înseamnă de obicei că trebuie schimbat designul rețelei, nu generatorul.
Verifică familia de adrese și domeniul tunelului
Rutele IPv4 nu fac nimic pentru IPv6. Dacă tunelul revendică o adresă IPv6, un prefix LAN IPv6 are nevoie de propriul complement, generat cu aceeași aritmetică pe 128 de biți. O cauză frecventă pentru care excluderea funcționează pe hârtie în timp ce dispozitivul local rămâne inaccesibil este aceasta: hostul a fost atins prin IPv6, iar doar IPv4 fusese decupat. Verifică ambele familii înainte de a concluziona că matematica este greșită.
Domeniul este cealaltă jumătate a verificării. Android urmărește rutarea pe rețea și pe aplicație, așa că același prefix poate merge prin tunel pentru o aplicație și direct pentru alta, în funcție de modul în care a fost declarat VPN-ul.
Pune regulile DNS și pe aplicație acolo unde le este locul
Excluderile de rută nu schimbă resolverul. Un nume de host local poate fi în continuare rezolvat de serverul DNS al tunelului în timp ce adresa sa este rutată local, iar nepotrivirea apare ca un nume care se rezolvă la ceva ce LAN-ul nu poate atinge. Setează politica resolverului deliberat, cu un domeniu de căutare sau o regulă de DNS divizat. Nu presupune că rutele au decis asta pentru tine.
addDisallowedApplication() ține o aplicație în afara tunelului; nu ține o destinație în afara pentru aplicațiile care rămân înăuntru. protect() lasă un socket pe care îl deschizi să ocolească tunelul, și astfel un tunel evită să-și captureze propriul transport. Niciuna nu înlocuiește complementul.
Verifică printr-o căutare, nu cu propria listă
Odată ce tunelul este sus, întreabă dispozitivul unde merge de fapt o adresă în loc să recitești lista pe care ai construit-o. Pe o compilare care livrează iproute2, adb shell ip route get 10.1.2.3 numește interfața aleasă de kernel. Rulează aceeași comandă pentru o adresă publică și confirmă că numește tunelul.
Încă două verificări închid bucla. Rezolvă mai întâi un nume local, apoi rulează adb shell ip route get pe adresa rezolvată și cere o interfață care nu este tunelul, pentru că un adb shell ping -c 1 host.lan reușit dovedește accesibilitatea, nu calea. Repetă testul familiei de adrese pe IPv6 dacă tunelul revendică una. Testează la nivelul de API pe care îl livrezi, pentru că modul în care rețelele VPN sunt expuse tabelului de rutare s-a schimbat între versiuni. O listă de rute care pare corectă pe hârtie și o căutare care alege interfața greșită sunt erori diferite, și doar a doua ajunge la utilizatori.
Dacă lista de excluderi continuă să crească, oprește generarea de rute și renumără rețeaua îndepărtată sau îngustează tunelul. Generatorul este o soluție de ocol pentru API-ul lipsă, nu un instrument de proiectare.