Kodėl Android VpnService pagauna tik dalį srauto, o ne visą
Maršrutai, DNS įrašai ir programėlės apimtis lemia, kurie paketai pasiekia tun įrenginį, kodėl dingsta vietiniai įrenginiai ir ko atsisako kūrėjas.
Niekas VpnService neperima srauto pats savaime. Jis paprašo sistemos tun sąsajos, perduoda jai adresų ir maršrutų rinkinį, ir nuo tos akimirkos branduolys nukopijuoja kiekvieną šiems maršrutams atitinkantį paketą į failo deskriptorių, kurį programėlė gali skaityti. Programėlės kodas niekada nemato paketų, kurių maršrutai nepasirinko, todėl tunelio apimtis yra maršruto lentelės klausimas anksčiau, nei tampa programavimo klausimu. Todėl ta pati paslaugos realizacija viename telefone gali praryti visą srautą, o kitame – tik dalį, ir todėl maršruto pridėjimas kartais sutaiso tunelį, kuris atrodė sugedęs. Gaunate tai, ką deklaravote, atėmus viską, ko kūrėjas tyliai atsisakė.
Maršrutai nusprendžia, kurie paketai atvyksta
Builder.addRoute(prefix, prefixLength) įdiegia prefiksą VPN tinklo maršruto lentelėje. Sistema traktuoja šį tinklą kaip numatytąjį apimties programėlėms, todėl jo įrašai tikrinami prieš fizinio tinklo numatytąjį maršrutą, ir atitinkamo prefikso pakanka srautui nukreipti. Todėl maršrutas 0.0.0.0/0 paima visą IPv4, įskaitant spausdintuvą, cast tikslą ir maršrutizatoriaus administravimo puslapį.
Klientai, norintys šios aprėpties neištrindami fizinio numatytojo įrašo, vietoj to naudoja porą 0.0.0.0/1 ir 128.0.0.0/1. Du konkretesni maršrutai įveikia numatytąjį maršrutą, kartu jie apima visą adresų erdvę, o paties telefono numatytasis įrašas lieka lentelėje lizdams, kurie vis dar susieti su fiziniu tinklu.
Adresai ir DNS įrašai ateina iš to paties kūrėjo ir atsako į kitą klausimą. addAddress suteikia sąsajai adresą su prefikso ilgiu, o addDnsServer įdiegia rezolverį, kurio apimties programėlės klaus per sąsają. Abi šeimos gali būti vienoje sąsajoje, todėl vien tik IPv4 maršrutų rinkinys šalia IPv6 adreso yra konfigūracija, kuri skaitoma teisingai ir vis tiek nuteka. Jei maršrutizuojate šeimą, kurioje sąsaja neturi adreso, branduolys neturi šių paketų šaltinio adreso, ir jie niekada nepasirodo deskriptoriuje.
Ką grąžina establish()
establish() grąžina ParcelFileDescriptor, arba null, kai programėlė neautorizuota. Null yra sutikimo būsena, o ne tinklo klaida: VpnService.prepare() jau turėjo grąžinti null, arba vartotojas turėjo patvirtinti sistemos dialogą prieš iškvietimą. Sutikimas gali būti atšauktas veikiant paslaugai, ir sąsaja užsidaro po jūsų kojomis be jokio atgalinio iškvietimo į jūsų skaitymo ciklą.
Deskriptorius neblokuoja, nebent nurodysite kitaip, todėl skaitymas grįžta iš karto be duomenų, o ne sustabdo giją. Ciklas, parašytas blokuojančiam lizdui, sudegina vieną branduolį, o ciklas, kuris tuščią skaitymą laiko mirtinu, per tuščią sekundę suplėšo tunelį. Builder.setBlocking(true) pakeičia šią elgseną, o kaina yra gija, kuri laukia skaitymo viduje.
Maršruto ir adreso argumentai tikrinami kaip LinkAddress reikšmės, o multicast paskirties vietos, tokios kaip 224.0.0.0/4, atmetamos iškart. Taškas-taškas tun sąsaja taip pat neturi transliacijos domeno, todėl multicast aptikimas per ją reikalauja kito dizaino, o ne dar vieno maršruto. Nuoroda VpnService.Builder yra sąrašas, kurį reikia patikrinti prieš darant išvadą, kad iškvietimas buvo ignoruotas.
Programėlės apimtis susiaurina rinkinį, o ne tunelį
Nuo API 21 kūrėjas priima addAllowedApplication ir addDisallowedApplication, ir abu sąrašai viename kūrėjyje vienas kitą išskiria. Leidžiamųjų sąrašas sukuria tunelį tiems paketams, o visa kita išlaiko fizinį tinklą; draudžiamųjų sąrašas pagauna viską, išskyrus įvardytus paketus, ką klientas siūlo kaip skaidytą tuneliavimą. Prieš API 21 platformos mechanizmo apskritai nėra, o elgsena pagal programėlę atėjo iš trečiųjų šalių klientų, kurie filtruodavo pagal UID savo paties peradresuotojuje. Šis skirtumas svarbus derinant, nes programėlės tunelis su vieno paketo leidžiamųjų sąrašu beveik negamina srauto, ir tai yra teisinga elgsena, o ne sugedęs maršrutas.
Kad ir kokia būtų apimtis, lizdas, kurį jūsų programėlė atidaro VPN serveriui, neturi keliauti tuo tuneliu, kurį ji stato. VpnService.protect(socket) pririša šį lizdą prie pagrindinio tinklo, kad jo paketai apeitų tun įrenginį. Jei praleisite iškvietimą, rankos paspaudimą prarys sąsaja, kuri turėjo jį pakelti.
VpnService.setUnderlyingNetworks uždaro ratą pasakydamas sistemai, kuris fizinis tinklas neša tunelį. Ši reikšmė lemia, kaip VPN praneša apie ryšį kitoms programėlėms, o null arba pasenusi reikšmė yra įprasta prijungto tunelio, kurį likęs telefonas laiko neprisijungusiu, priežastis. Atnaujinkite ją, kai keičiasi pagrindinis tinklas, ne tik paleidžiant.
Kontrolinės sumos ir MTU: kur teisingas maršrutas vis tiek nepavyksta
Kai paketai atvyksta, dvi detalės paketo lygiu nusprendžia, ar kas nors veikia nuo galo iki galo. Pirmoji yra šaltinio adresas: esant keliems sąsajos adresams branduolys gali siųsti paketus, kurie neša pirmąjį, todėl peradresuotojas, perrašantis antraštes, turi pataisyti adresą ir jį dengiančią kontrolinę sumą.
Antroji yra kontrolinės sumos apimtis. Kai programėlė pati konstruoja TCP arba UDP paketus, kontrolinė suma dengia pseudo-antraštę su šaltinio adresu, paskirties adresu, protokolo numeriu ir transporto ilgiu, o praleidus bet kurį iš jų gaunami paketai, kurie atvyksta ir yra išmetami. Ta nesėkmė atrodo kaip laiko limitas po atsakymo, kuris niekada neateina, ir tai daugumą nukreipia atgal prie maršrutų. setMtu fiksuoja sąsajos MTU, ir kiekvienas skaitymas grąžina lygiai vieną visą IP paketą, todėl baitų srautui parašytas buferis suskaidys jūsų analizę, o ne paketą.
Patikros važiavimas, atskiriantis sluoksnius
Norėdami rasti, kur apimtis pakrypo ne ten, išbandykite sluoksnius iš eilės. Paklauskite įrenginio, kokius maršrutus VPN tinklas iš tikrųjų turi, tada patikrinkite, ar srautas į žinomą adresą pasirodo kaip skaitymas deskriptoriuje, tada patikrinkite, ar paketas, kurį siunčia jūsų peradresuotojas, yra tinkamai suformuotas. Maršruto problema deskriptoriuje nieko nerodo, o kontrolinės sumos problema rodo paketą, kuris atvyksta ir dingsta.
Jei niekas nepasiekia deskriptoriaus, įtariamasis yra maršrutų rinkinys, o ne skaitytojas: patvirtinkite prefiksus, patvirtinkite, kad toje pačioje šeimoje yra adresas, ir patvirtinkite, kad programėlė yra jūsų nustatytos apimties viduje. Jei paketai pasiekia deskriptorių, bet niekas negrįžta, patikrinkite protect() ir pagrindinio tinklo reikšmę prieš vėl liesdami maršrutą. Jei tolimoji pusė praneša apie sugadintus paketus, kontrolinė suma ir MTU yra vietos, kur reikia žiūrėti. Vienas bandymas vienam sluoksniui sutaupo popietę spėlionių.