Android VPN: VpnService-deskriptor tsjin in root-tun-apparaat
Android-apps kinne tun0 net sels meitsje: wat establish() weromjout, wêr't Binder ophâldt en JNI begjint, en wannear't in troch root makke apparaat wint.
Twa paden kinne in IP-ynterface foar in Android-app sette, en beide einigje by itselde soart bestânsdeskriptor mei hiel ferskillende eigners. De iene rint fia VpnService en syn bouwer, de oare fia in root-shell en /dev/net/tun. Elke lêzing op dy deskriptor is in útgeand pakket en elke skriuwing is in yngeand. Wat de paden skiedt is wa't it belied hat, wa't in OS-opwurdearring oerlibbet, en hoefolle fan de rûtetabel do oanreitsje meie.
Nim it platfoarmpaad, útsein as do it spesifike ding neame kinst dat it wegeret te dwaan. Dy wegering is normaal TAP, opname bûten dyn eigen app-set, of in rûtereel dy't it ramt nea bleatleit.
Op it platfoarmpaad ferklearje jo bedoeling ynstee fan opdrachten. Jo jouwe teminsten ien adres, de rûtes dy't jo wolle, DNS-tsjinners en in opsjonele app-list, en dan docht Android syn systeemtsjinst de privilegearrestelde opset ûnder syn eigen identiteit. Jo leare de ynterfacenamme nea foarôf, en jo stjoere nea de ioctl út dy't it makket, en dat is it punt.
Twa manieren om in IP-ynterface te krijen
It rootpaad is it âldere resept. Jo iepenje /dev/net/tun, roppe TUNSETIFF op mei IFF_TUN, bringe de ynterface omheech mei ip link, en foegje adressen en rûtes mei de hân ta. Eltse stap is dyn om goed te krijen, ek dy't it ramt stil ôfhannele hie. Ien ûntbrekkende rûte jout deselde brûkersrapportage as in deade tsjinner.
Debugsjen ferskilt likefolle as opset. Op it rootpaad bestiet de ynterface of jo ioctl joech in flater werom dy't jo direkt lêze kinne. Op it platfoarmpaad bestiet de ynterface pas neidat establish() slagge is, dus in mislearring komt as in útsûndering of in null-deskriptor ynstee fan in apparaatknooppunt dêr't jo oan peuterje kinne.
Wat establish() eins weromjout
Builder.establish() hat trije útkomsten en dy freegje trije ferskillende antwarten. In deskriptor betsjut dat de ynterface konfigurearre is en fan dy om te brûken, null betsjut dat it fersyk net trochgie, en in smiten útsûndering betsjut dat de systeemtsjinst it wegere. null en súkses yn ien boolean gearfoegje is de meast foarkommende manier wêrop in app in wurkjende tunnel meldt dy't nea in pakket draacht. De Builder-referinsje neamt de adresfoardering, en dat is it lêzen wurdich foardat jo de tsjinner de skuld jouwe.
De rjochtingen wurde troch de deskriptor fêstlein, net troch dyn protokol. Lêzingen jouwe jo útgeande pakketten dy't al foar de VPN keazen binne, en skriuwingen spuitsje yngeande pakketten werom yn 'e ynterface. Dyn eigen transport fersiferet, ferifiearret en ferpaktet dy pakketten, om't it platfoarm by de pakketgrins ophâldt.
Binder is ien sprong, JNI is in oare as
In bouwerfersyk giet it systeem yn fia Binder, en earne dêrûnder ropt it platfoarm nativen koade oan om it apparaat te konfigurearjen. Dat binne twa ferskillende meganismen dy't yn ien oprop opsteapele binne. Binder is ynterproseskommunikaasje tusken dyn app en in systeemtsjinst, wylst JNI de brêge fan Java nei nativen koade yn in proses is. In Android 9 AOSP-beam lêze lit de stapel letterlik lykje, en it is it wurdich om te betinken dat ynterne klasnammen en bestânspaden tusken útjeften ferskowe.
Wat net ferskowt is it kontrakt dêr't jo op fertrouwe meie: de bouwer, de deskriptor dy't er weromjout, en de libbenssyklus fan de tsjinst deromhinne. Gjin app soe in ynterne tsjinsttransaksje oanroppe of in apparaatknooppunt opnij meitsje moatte om in ynterface te krijen. As dyn koade fan in AOSP-klasnamme ôfhinget, sil it brekke op in útjefte dy't jo net test hawwe.
TUN draacht IP, TAP draacht Ethernet
TUN is in IP-ynterface. TAP is in Ethernet-ynterface, en dat ferskil beslút oft oerbrêgjen oeral mooglik is. In TUN-apparaat hat gjin MAC-adres en gjin Ethernet-kop om troch te stjoeren, dus in TUN-klant kin net by in laach-2-brêge komme, hoe't de rûtes ek skreaun binne.
VpnService jout jo de IP-fariant, dat is wat in VPN op app-nivo nedich hat en wat it stypje kin sûnder in rûge Ethernet-stapel. As dyn ûntwerp oerbrêgjen fereasket, sil it platfoarmpaad jo dêr net krije, en it rootpaad allinnich as de kearn en de apparaatrjochten gearwurkje.
Wêr't it rootpaad noch wint
Trije taken skowe minsken fan it platfoarmpaad ôf. Laach-2-oerbrêgjen hat TAP nedich. Ferstjoer fan apps bûten dyn eigen VPN-berik opfange hat sichtberens nedich dy't it ramt net jout. Beliedsrûtering dy't de bouwer nea bleatleit, lykas in aparte tabel foar ien bestimming, hat regels nedich dy't jo sels ynstallearje.
De kosten komme letter: in ûntskoattele bootloader of in rootte byld, SELinux-belied dat ûnder jo ferskowt, en in apparaat dat nei in OTA net mear goed docht. In rootte resept kin ek net fia normale app-distribúsje útstjoerd wurde, dus de measte teams hâlde it as in yntern diagnostysk paad ynstee fan in produkt.
Kieze tusken beide
Begjin by de ynterface dy't jo nedich hawwe, net by de koade dy't jo hawwe. As it antwurd in TUN-apparaat mei app-beheinde rûtes is, is it platfoarmpaad koarter en oerlibbet it opwurdearrings. As it antwurd Ethernet-frames, opname fan oare apps of rûteregels omfettet dy't de bouwer wegeret, binne jo op root-territoarium en moatte jo dêrfoar operasjonele kosten begreatsje.
In kontrôle dy't de measte diskusjes besljochtet: bou earst it platfoarmpaad, registrearje de útkomst fan establish() apart fan 'e tastân fan 'e ôfstâne sesje, en befêstigje ien pakket yn elke rjochting. Allinnich dan beslute oft de ûntbrekkende kapasiteit it ferlitten fan it stipe paad rjochtfeardiget.