Hoe om 'n subnet uit 'n Android VPN-tonnel uit te sluit
VpnService kan nie 'n roete uitsluit nie, dus voeg jy die komplement by: agt gegenereerde prefikse vir 'n /8, plus die IPv6-, DNS- en opsoekkontroles wat die res vang.
Android se VpnService het geen uitsluitroete nie, en geen hoeveelheid API-leeswerk gaan een voortbring nie. Builder.addRoute() voeg altyd net bestemmings by die tonnel, so om 'n LAN-segment plaaslik te hou beteken dat daardie segment nooit die roetelys bereik nie. Wat jy eerder byvoeg, is die komplement van die segment, uitgedruk in gewone prefikse. Die werk is rekenwerk, en dit misluk op 'n paar voorspelbare plekke.
Werk die kontroles hieronder in volgorde deur. Elkeen veronderstel dat die vorige gegeld het, so stop by die eerste mislukking eerder as om simptome verder af te pleister.
Begin by wat die tonnel eis
As jy 0.0.0.0/0 aan addRoute() gee, sê jy vir Android dat elke IPv4-bestemming aan die tonnel behoort. Dit is waarom so baie kliënte onbekwaam lyk vir split-tonneling: hulle eis die volle adresruimte en probeer dan daarvan aftrek. Niks in die bouer trek af nie. 'n Meer spesifieke roete vir die netwerk wat jy plaaslik wou hou, doen die teenoorgestelde van wat mense verwag, want die langste ooreenstemmende prefiks wen en daardie netwerk land binne die tonnel.
Die roetelys is die hele beleid. Daar is geen per-bestemming-veto agter dit nie, en die Builder-verwysing noem geen metode wat een sou verskaf nie.
Genereer die komplement uit prefikse-rekenwerk
Die komplement van 'n uitgeslote prefiks is die versameling van sy broers op die pad na die wortel van die adresboom. Vir elke lengte van 1 tot by die uitgeslote lengte, neem die blok van daardie lengte wat jou prefiks bevat, draai die bis net onder die prefiks om, en lewer die resultaat. Dit klink abstrak totdat jy dit uitvoer.
As jy 10.0.0.0/8 uitsluit, kry jy agt roetes: 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. Nie een van hulle bevat 10.0.0.0/8 nie, en saam dek hulle alles anders.
Agt reëls genereer daardie lys vir enige prefiks:
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))
Die aantal roetes is gelyk aan die prefikslengte, en dit is die getal om dop te hou. 'n Enkele /21-uitsluiting kos 21 roetes en bly leesbaar. Tien uitsluitings kan 'n paar honderd kos, wat gewoonlik beteken die netwerkontwerp moet verander eerder as die generator.
Kontroleer die adresfamilie en die tonnelbereik
IPv4-roetes doen niks vir IPv6 nie. As die tonnel 'n IPv6-adres eis, het 'n IPv6-LAN-prefiks sy eie komplement nodig, gegenereer met dieselfde rekenwerk oor 128 bits. 'n Algemene oorsaak dat die uitsluiting op papier werk terwyl die plaaslike toestel onbereikbaar bly, is dit: die gasheer is oor IPv6 bereik, en net IPv4 was uitgekerf. Kontroleer beide families voordat jy tot die gevolgtrekking kom dat die wiskunde verkeerd is.
Bereik is die ander helfte van die kontrole. Android volg roetering per netwerk en per toepassing, so dieselfde prefiks kan vir een toepassing deur die tonnel gaan en vir 'n ander direk wees, afhangend van hoe die VPN verklaar is.
Plaas DNS- en per-toepassing-reëls waar hulle hoort
Roete-uitsluitings verander nie die resolver nie. 'n Plaaslike gasheernaam kan steeds deur die tonnel se DNS-bediener beantwoord word terwyl sy adres plaaslik gerouteer word, en die wanverhouding wys as 'n naam wat oplos na iets wat die LAN nie kan bereik nie. Stel resolver-beleid doelbewus in, met 'n soekdomein of 'n split-DNS-reël. Moenie aanneem die roetes het dit vir jou besluit nie.
addDisallowedApplication() hou een toepassing uit die tonnel; dit hou nie 'n bestemming uit vir die toepassings wat binne bly nie. protect() laat 'n sokkel wat jy oopmaak, die tonnel omseil, en so vermy 'n tonnel dat sy eie vervoer gevang word. Nie een van die twee vervang die komplement nie.
Verifieer met 'n opsoek, nie met jou eie lys nie
Sodra die tonnel op is, vra die toestel waar 'n adres werklik heen gaan, eerder as om die lys wat jy gebou het weer te lees. Op 'n bou waar iproute2 saamkom, noem adb shell ip route get 10.1.2.3 die koppelvlak wat die kern gekies het. Voer dieselfde opdrag uit vir 'n publieke adres en bevestig dat dit die tonnel noem.
Twee verdere kontroles sluit die lus. Los eers 'n plaaslike naam op, voer dan adb shell ip route get op die opgeloste adres uit en eis 'n koppelvlak wat nie die tonnel is nie, want 'n suksesvolle adb shell ping -c 1 host.lan bewys bereikbaarheid en nie die pad nie. Herhaal die adresfamilie-toets oor IPv6 as die tonnel een eis. Toets op die API-vlak wat jy lewer, want hoe VPN-netwerke aan die roetertabel blootgestel word, het oor uitgawes verander. 'n Roetelys wat op papier reg lyk en 'n opsoek wat die verkeerde koppelvlak kies, is verskillende foute, en net die tweede een bereik gebruikers.
As die uitsluitlys aanhou groei, stop met die genereer van roetes en hernommer die afgeleë netwerk of vernou die tonnel eerder. Die generator is 'n werkoplossing vir die ontbrekende API, nie 'n ontwerpgereedskap nie.