Android VPN: përshkruesi VpnService kundrejt një pajisjeje tun root
Aplikacionet Android nuk mund të krijojnë vetë tun0: çfarë kthen establish(), ku ndalon Binder dhe ku fillon JNI, dhe kur fiton një pajisje e krijuar nga root.
Dy rrugë mund të vendosin një ndërfaqe IP përpara një aplikacioni Android, dhe të dyja përfundojnë në të njëjtin lloj përshkruesi skedari me pronarë shumë të ndryshëm. Njëra kalon përmes VpnService dhe builder-it të tij, tjetra përmes një shell root dhe /dev/net/tun. Çdo lexim në atë përshkrues është një paketë dalëse dhe çdo shkrim është një paketë hyrëse. Ajo që i ndan rrugët është kush e mban politikën, kush i mbijeton një përditësimi të OS-it dhe sa nga tabela e rrugëtimit mund të prekësh.
Zgjidh rrugën e platformës, përveç nëse mund të emërtosh gjënë konkretë që ajo refuzon ta bëjë. Ai refuzim është zakonisht TAP, kapja jashtë grupit tënd të aplikacioneve, ose një rregull rrugëtimi që framework-u nuk e zbulon kurrë.
Në rrugën e platformës deklaron qëllimin në vend të komandave. Jep të paktën një adresë, rrugët që dëshiron, serverë DNS dhe një listë opsionale aplikacionesh, pastaj shërbimi i sistemit të Android kryen konfigurimin e privilegjuar nën identitetin e tij. Nuk e mëson kurrë emrin e ndërfaqes përpara dhe nuk lëshon kurrë ioctl-in që e krijon, dhe pikërisht kjo është çështja.
Dy mënyra për të marrë një ndërfaqe IP
Rruga root është receta më e vjetër. Hap /dev/net/tun, thërret TUNSETIFF me IFF_TUN, ngre ndërfaqen me ip link, dhe shton adresat e rrugët me dorë. Çdo hap është i yti për ta bërë saktë, përfshirë ata që framework-u do t'i kishte trajtuar në heshtje. Një rrugë që mungon i jep përdoruesit të njëjtin raport si një server i vdekur.
Debugimi ndryshon aq sa konfigurimi. Në rrugën root ndërfaqja ose ekziston, ose ioctl-i yt ka kthyer një gabim që mund ta lexosh drejtpërdrejt. Në rrugën e platformës ndërfaqja ekziston vetëm pasi establish() të ketë sukses, kështu që një dështim vjen si përjashtim ose si përshkrues null në vend të një nyje pajisjeje ku mund të gërmosh.
Çfarë kthen në të vërtetë establish()
Builder.establish() ka tre rezultate dhe ato kërkojnë tre përgjigje të ndryshme. Një përshkrues do të thotë që ndërfaqja është konfiguruar dhe është e jotja për t'u përdorur, null do të thotë që kërkesa nuk kaloi, dhe një përjashtim i hedhur do të thotë që shërbimi i sistemit e refuzoi. Bashkimi i null dhe suksesit në një boolean të vetëm është mënyra më e zakonshme me të cilën një aplikacion raporton një tunel që funksionon dhe që nuk mbart kurrë një paketë. Referenca e Builder shpreh kërkesën për adresë, dhe ia vlen ta lexosh përpara se të fajësosh serverin.
Drejtimet i cakton përshkruesi, jo protokolli yt. Leximet të japin paketa dalëse të zgjedhura tashmë për VPN-in, dhe shkrimet injektojnë paketa hyrëse përsëri në ndërfaqe. Transporti yt kripton, vërteton dhe kornizon ato paketa, sepse platforma ndalon në kufirin e paketës.
Binder është një hap, JNI është një bosht tjetër
Një kërkesë builder-i kalon në sistem përmes Binder, dhe diku poshtë tij platforma thërret kod nativ për të konfiguruar pajisjen. Këto janë dy mekanizma të ndryshëm të grumbulluar në një thirrje të vetme. Binder është komunikim ndërmjet proceseve midis aplikacionit tënd dhe një shërbimi sistemi, ndërsa JNI është ura nga Java në kodin nativ brenda një procesi. Leximi i një peme AOSP të Android 9 e bën stack-un literal, dhe ia vlen të mbahet mend se emrat e brendshëm të klasave dhe shtigjet e skedarëve lëvizin midis botimeve.
Ajo që nuk lëviz është kontrata në të cilën mund të mbështetesh: builder-i, përshkruesi që kthen dhe cikli i jetës së shërbimit rreth tij. Asnjë aplikacion nuk duhet të thërrasë një transaksion të brendshëm shërbimi ose të rikrijojë një nyje pajisjeje për të marrë një ndërfaqe. Nëse kodi yt varet nga një emër klase i AOSP, ai do të thyhet në një botim që nuk e ke testuar.
TUN mbart IP, TAP mbart Ethernet
TUN është një ndërfaqe IP. TAP është një ndërfaqe Ethernet, dhe ai dallim vendos nëse ura është e mundur fare. Një pajisje TUN nuk ka adresë MAC dhe nuk ka kokë Ethernet për të përcjellë, kështu që një klient TUN nuk mund t'i bashkohet një ure të shtresës 2, sido që të jenë shkruar rrugët.
VpnService të jep variantin IP, që është ajo që i duhet një VPN-i në nivel aplikacioni dhe që ai mund ta mbështesë pa një stack të papërpunuar Ethernet. Nëse dizajni yt kërkon urë, rruga e platformës nuk të çon atje, dhe rruga root vetëm nëse kernel-i dhe lejet e pajisjes bashkëpunojnë.
Ku rruga root ende fiton
Tre punë i shtyjnë njerëzit larg rrugës së platformës. Ura e shtresës 2 kërkon TAP. Kapja e trafikut nga aplikacione jashtë fushës tënde të VPN kërkon dukshmëri që framework-u nuk e jep. Rrugëtimi me politikë që builder-i nuk e zbulon kurrë, si një tabelë e veçantë për një destinacion, kërkon rregulla që i instalon vetë.
Kostot vijnë më vonë: një bootloader i zhbllokuar ose një imazh me root, një politikë SELinux që lëviz nën ty, dhe një pajisje që pushon së sjelluri saktë pas një OTA. Një recetë me root gjithashtu nuk mund të shpërndahet përmes shpërndarjes normale të aplikacioneve, kështu që shumica e ekipeve e mbajnë si rrugë diagnostikuese të brendshme dhe jo si produkt.
Zgjedhja mes tyre
Fillo nga ndërfaqja që të duhet, jo nga kodi që ke. Nëse përgjigja është një pajisje TUN me rrugë të kufizuara tek aplikacioni, rruga e platformës është më e shkurtër dhe i mbijeton përditësimeve. Nëse përgjigja përfshin korniza Ethernet, kapjen e aplikacioneve të tjera ose rregulla rrugëtimi që builder-i i refuzon, je në territorin root dhe duhet ta parashikosh si kosto operacionale.
Një kontroll që zgjidh shumicën e debateve: ndërto fillimisht rrugën e platformës, regjistro rezultatin e establish() veçmas nga gjendja e sesionit në distancë, dhe konfirmo një paketë në çdo drejtim. Vetëm atëherë vendos nëse aftësia që mungon e justifikon largimin nga rruga e mbështetur.