Miksi Android VpnService -tunnelisi lakkaa reitittämästä liikennettä
Neljä VpnService-uskomusta, jotka rikkoutuvat tuotannossa: yksi tunneli kerrallaan, reittien laajuus, sovelluskohtaiset luettelot ja sammutusjärjestys, kullekin oma tarkistus.
Neljä oletusta Android VpnServicestä maksoivat minulle kerran suurimman osan viikosta, eikä yksikään niistä koskenut verkkoprotokollaa. Sovellus ilmoitti olevansa yhteydessä, avainkuvake istui tilarivillä, ja liikenne lähti silti normaalireittiä pitkin. Oletin jatkuvasti, että tunneli on minun, kunnes pysäytän sen, että lisäämäni reitit ovat ne reitit, jotka saan, ja että stopSelf-kutsu ottaisi rajapinnan mukaansa. Palvelu käyttäytyi täsmälleen kuten on dokumentoitu. Rikkinäinen osa oli minun mielikuvani.
Myytti 1: tunneli pysyy pystyssä, kunnes pysäytät sen
Android pitää yhtä VPN-rajapintaa kerrallaan. Perusta toinen, niin alusta ei laita sitä jonoon vaan poistaa käytöstä sinun ja kutsuu palvelussasi onRevoke(). Jos koodisi purkaa kaiken vain painikkeen painalluksesta, haltuunotto jättää jälkeensä deskriptorin ja lukusäikeen, ja seuraava käynnistys perii molemmat.
VpnService-viite sanoo selvästi, että prepare() palauttaa null, kun sovelluksella on jo suostumus, ja Intent, kun käyttäjän on vielä hyväksyttävä se. Se hyväksyntä saapuu vain, jos toimitat aikeen startActivityForResult() kautta. Ohita luovutus, niin establish() palauttaa null, mikä luetaan hiljaiseksi epäonnistumiseksi eikä käyttöoikeusongelmaksi.
Always-on VPN lisää omistajan, jota et hallitse. Järjestelmä voi palauttaa tunnelin käynnistyksen tai kaatumisen jälkeen, ja se syrjäyttää VPN-sovelluksen, jonka käyttäjä käynnisti käsin. Rakenna onRevoke() niin, että se sulkee samat resurssit kuin tavallinen sammutuksesi, ja kohtele sitä ensisijaisena pysäytyspolkuna eikä reunatapauksena. Käyttäjät vaihtavat VPN-sovelluksia; koodisi on selvittävä siitä.
Myytti 2: addRoute sieppaa täsmälleen sen, mitä luetteloit
Reitit päättävät, mitkä paketit saapuvat deskriptorillesi, ja laajuusvirhe tuottaa hulluksi tekevän tapauksen, jossa osa liikenteestä kulkee tunnelin läpi ja osa ei. addRoute("0.0.0.0", 0) ottaa kaiken IPv4:n, eli jokainen paketti saapuu nyt sinulle ja se on välitettävä tai pudotettava. Yhden isännän /32 ei ota juuri mitään, ja kaikki muu käyttäytyy kuin VPN:ääsi ei olisi koskaan asennettu.
Perheet lasketaan erikseen. Vain IPv4-reitti jättää IPv6:n taustaverkkoon, joten kaksoispinoisäntä voi yhä selvittää ja yhdistää tunnelin ulkopuolelle näyttäen samalla sisällä täysin yhdistetyltä.
DNS seuraa samaa logiikkaa. addDnsServer():lla lisätyt palvelimet menevät sovelluksille, jotka käyttävät tunnelin ratkaisijaa, kun taas sovellus, jolla on oma salattu DNS-päätepiste tai isäntänimeen asetettu Private DNS, jättää ne kokonaan huomiotta. Kun nimet epäonnistuvat yhdellä sovelluksella ja toimivat toisella, tarkista ratkaisijan polku ennen kuin kosket yhtäkään reittiä.
Myytti 3: sovelluskohtaiset säännöt ovat kytkin, jonka voi kääntää kumpaan suuntaan tahansa
addDisallowedApplication() ja addAllowedApplication() ovat kaksi tilaa, eivät kaksi suodatinta. Rakentaja hyväksyy yhden mallin tunnelia kohden, joten sovellus, joka haluaa kaiken paitsi kolme pakettia, käyttää kiellettyä luetteloa ja jättää sallitun luettelon tyhjäksi. Nämä kaksi sulkevat toisensa pois, ja ihmiset huomaavat sen ajonaikana sen jälkeen, kun asetusnäyttö on jo tarjonnut molemmat kytkimet.
Pakettien näkyvyys puree myös. Android 11:ssä ja uudemmissa voit nimetä vain paketteja, jotka näet, joten poissulkuluettelo, joka on rakennettu kyselystä, jota sovelluksesi ei saa suorittaa, jättää merkinnät hiljaa pois. Vertaa luetteloa siihen, mitä paketinhallinta todella palauttaa, ennen kuin luotat siihen.
Työprofiilit jakavat yleisön taas. Profiilin sisällä käynnistetty tunneli kattaa kyseisen profiilin ja sen sovellukset, kun taas henkilökohtainen puoli pitää oman yhteytensä. Se on kerran tehty laajuuspäätös, ja se kuuluu käyttöliittymään, ei tukipyyntöön.
Myytti 4: stopSelf lopettaa tunnelin
Ei lopeta. Rajapinta elää deskriptorissa, jonka establish() palauttaa, ja palveluobjekti voi kuolla, kun deskriptori jää auki. Sulje ensin ParcelFileDescriptor, pudota viittaus, liitä tai keskeytä luku- ja kirjoitussäikeet ja kutsu sitten stopForeground(true) ja stopSelf() tässä järjestyksessä.
Unohdettu deskriptori on tavallinen syy siihen, että palvelu näyttää selviävän omasta pysäytyspyynnöstään, ja se selittää vanhentuneen reitin, joka elää kauemmin kuin sen lisännyt sovellus.
Käänteinen virhe on yhtä yleinen: deskriptori suljetaan, kun lukusäie on kesken lukemisen, ja sitten se säie jätetään käsittelemään poikkeusta, jota kukaan ei kirjoittanut. Pidä sammutusjärjestys yhdessä paikassa ja anna sekä käyttäjän toiminnon että onRevoke() kutsua siihen.
Mitä tarkistaa, kun tunneli näyttää kuolleelta
Suojaa pitkäikäiset pistokkeet protect():lla ennen kuin tunneli nousee. Ilman sitä tunnelin oma kuljetus reititetään takaisin tunneliin ja yhteys juuttuu tavalla, joka näyttää täsmälleen palvelinvialta. Testaa osoitetta vastaan, joka on olemassa vain etäverkon takana, ei julkista sivustoa vastaan, joka vastaa välimuistista tai taustapolulta.
Kirjaa establish():n tulos, reitit, poissuljetut paketit ja sammutuksen syy. Ne neljä riviä erottavat suostumusongelman laajuusongelmasta ja elinkaariongelmasta ilman pakettikaappausta.