Androidin VpnService ei tunne poissulkureittiä, eikä mikään määrä API:n lukemista tuota sellaista. Builder.addRoute() lisää tunneliin aina vain kohteita, joten LAN-segmentin pitäminen paikallisena tarkoittaa, että se segmentti ei koskaan päädy reittilistaan. Sen sijaan lisäät segmentin komplementin tavallisina etuliitteinä. Työ on aritmetiikkaa, ja se epäonnistuu muutamassa ennustettavassa kohdassa.

Käy alla olevat tarkistukset järjestyksessä läpi. Jokainen olettaa edellisen pitäneen, joten pysähdy ensimmäiseen epäonnistumiseen sen sijaan että paikkaisit oireita alempana.

Aloita siitä, mitä tunneli väittää

0.0.0.0/0:n välittäminen addRoute():lle kertoo Androidille, että jokainen IPv4-kohde kuuluu tunneliin. Siksi niin moni asiakasohjelma näyttää kykenemättömältä jaettuun tunnelointiin: ne vaativat koko osoiteavaruuden ja yrittävät sitten vähentää siitä. Mikään rakentajassa ei vähennä. Tarkempi reitti verkolle, jonka halusit pitää paikallisena, tekee päinvastoin kuin odotetaan, koska pisimpään täsmäävä etuliite voittaa ja se verkko päätyy tunnelin sisään.

Reittilista on koko käytäntö. Sen takana ei ole kohdekohtaista vetoa, eikä Builder-viite luettele menetelmää, joka sellaisen tarjoaisi.

Luo komplementti etuliitearitmetiikasta

Poissuljetun etuliitteen komplementti on sen sisarusten joukko polulla osoitepuun juureen. Ota jokaiselle pituudelle 1:stä poissuljettuun pituuteen asti kyseisen pituinen lohko, joka sisältää etuliitteesi, käännä bitti välittömästi etuliitteen alapuolella ja tulosta tulos. Se kuulostaa abstraktilta, kunnes ajat sen.

10.0.0.0/8:n poissulkeminen antaa kahdeksan reittiä: 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. Mikään niistä ei sisällä 10.0.0.0/8:ta, ja yhdessä ne kattavat kaiken muun.

Kahdeksan riviä luo tuon listan mille tahansa etuliitteelle:

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

Reittien määrä vastaa etuliitteen pituutta, ja se on luku, jota kannattaa seurata. Yksi /21-poissulkeminen maksaa 21 reittiä ja pysyy luettavana. Kymmenen poissulkemista voi maksaa pari sataa, mikä yleensä tarkoittaa, että verkkosuunnitelmaa pitää muuttaa eikä generaattoria.

Tarkista osoiteperhe ja tunnelin laajuus

IPv4-reitit eivät tee IPv6:lle mitään. Jos tunneli vaatii IPv6-osoitteen, IPv6-LAN-etuliite tarvitsee oman komplementtinsa, joka luodaan samalla aritmetiikalla 128 bitin yli. Yleinen syy siihen, että poissulkeminen toimii paperilla mutta paikallinen laite jää tavoittamattomaksi, on tämä: isäntään päästiin IPv6:n kautta ja vain IPv4 oli leikattu pois. Tarkista molemmat perheet ennen kuin päättelet, että matematiikka on väärin.

Laajuus on tarkistuksen toinen puoli. Android seuraa reititystä verkkokohtaisesti ja sovelluskohtaisesti, joten sama etuliite voi kulkea tunnelin kautta yhdelle sovellukselle ja suoraan toiselle riippuen siitä, miten VPN on julistettu.

Sijoita DNS- ja sovelluskohtaiset säännöt sinne, minne ne kuuluvat

Reittien poissulkemiset eivät muuta ratkaisijaa. Paikalliseen isäntänimeen voi yhä vastata tunnelin DNS-palvelin, vaikka sen osoite reititetään paikallisesti, ja epäsuhta näkyy nimenä, joka ratkeaa joksikin, mihin LAN ei yllä. Aseta ratkaisijan käytäntö tarkoituksella, hakutoimialueella tai jaetun DNS:n säännöllä. Älä oleta, että reitit päättivät sen puolestasi.

addDisallowedApplication() pitää yhden sovelluksen tunnelin ulkopuolella; se ei pidä kohdetta ulkopuolella niille sovelluksille, jotka jäävät sisään. protect() sallii avaamasi pistokkeen ohittaa tunnelin, ja niin tunneli välttää oman liikenteensä nappaamisen. Kumpikaan ei korvaa komplementtia.

Varmista haulla, älä omalla listallasi

Kun tunneli on ylhäällä, kysy laitteelta, minne osoite todella menee, sen sijaan että lukisit rakentamasi listan uudelleen. Buildissa, joka toimittaa iproute2:n, adb shell ip route get 10.1.2.3 nimeää rajapinnan, jonka ydin valitsi. Aja sama komento julkiselle osoitteelle ja varmista, että se nimeää tunnelin.

Kaksi tarkistusta sulkee silmukan. Ratkaise ensin paikallinen nimi, aja sitten adb shell ip route get ratkaistulle osoitteelle ja vaadi rajapinta, joka ei ole tunneli, koska onnistunut adb shell ping -c 1 host.lan todistaa tavoitettavuuden eikä polun. Toista osoiteperheen testi IPv6:n yli, jos tunneli vaatii sellaisen. Testaa sillä API-tasolla, jonka toimitat, koska tapa, jolla VPN-verkot altistetaan reititystaululle, on muuttunut julkaisujen välillä. Paperilla oikealta näyttävä reittilista ja väärän rajapinnan valitseva haku ovat eri vikoja, ja vain jälkimmäinen saavuttaa käyttäjät.

Jos poissulkemislista jatkaa kasvuaan, lopeta reittien luominen ja numeroi etäverkko uudelleen tai kavenna tunnelia sen sijaan. Generaattori on puuttuvan API:n kiertotie, ei suunnittelutyökalu.

Aiheeseen liittyvät artikkelit