Android VPN: priskribilo de VpnService kontraŭ root-tun-aparato
Android-aplikaĵoj ne povas mem krei tun0: kion establish() redonas, kie Binder haltas kaj JNI komenciĝas, kaj kiam root-kreita aparato venkas.
Du vojoj povas meti IP-interfacon antaŭ Android-aplikaĵon, kaj ambaŭ finiĝas ĉe la sama speco de dosiera priskribilo kun tre malsamaj posedantoj. Unu iras tra VpnService kaj ĝia konstruilo, la alia tra root-ŝelo kaj /dev/net/tun. Ĉiu legado sur tiu priskribilo estas elira pako kaj ĉiu skribado estas enira. Kio apartigas la vojojn estas kiu tenas la politikon, kiu postvivas OS-ĝisdatigon, kaj kiom de la enkursigilo vi rajtas tuŝi.
Prenu la platforman vojon krom se vi povas nomi la konkretan aferon, kiun ĝi rifuzas fari. Tiu rifuzo kutime estas TAP, kaptado ekster via propra aro da aplikaĵoj, aŭ enkursiga regulo, kiun la kadro neniam malkaŝas.
Sur la platforma vojo vi deklaras intencon anstataŭ komandojn. Vi donas almenaŭ unu adreson, la deziratajn itinerojn, DNS-servilojn kaj laŭvolan liston da aplikaĵoj, poste la sistema servo de Android plenumas la privilegiitan agordon sub sia propra identeco. Vi neniam anticipe ekscias la nomon de la interfaco, kaj vi neniam eldonas la ioctl, kiu kreas ĝin, kaj tio estas la punkto.
Du manieroj akiri IP-interfacon
La root-vojo estas la pli malnova recepto. Vi malfermas /dev/net/tun, vokas TUNSETIFF kun IFF_TUN, levigas la interfacon per ip link, kaj aldonas adresojn kaj itinerojn permane. Ĉiu paŝo estas via por fari ĝin ĝuste, inkluzive tiujn, kiujn la kadro silentus. Unu mankanta itinero produktas la saman uzantan raporton kiel morta servilo.
Senerarigado malsamas same multe kiel agordo. Sur la root-vojo la interfaco aŭ ekzistas, aŭ via ioctl redonis eraron, kiun vi povas legi rekte. Sur la platforma vojo la interfaco ekzistas nur post kiam establish() sukcesas, do malsukceso alvenas kiel escepto aŭ kiel null-priskribilo anstataŭ kiel aparatnodo, kiun vi povas fosi.
Kion establish() efektive redonas
Builder.establish() havas tri eblajn rezultojn, kaj ili bezonas tri malsamajn respondojn. Priskribilo signifas, ke la interfaco estas agordita kaj via por uzi, null signifas, ke la peto ne trapasis, kaj ĵetita escepto signifas, ke la sistema servo rifuzis ĝin. Kunfandi null kaj sukceson en unu boolean estas la plej kutima maniero, per kiu aplikaĵo raportas funkciantan tunelon, kiu neniam portas pakon. La Builder-referenco indikas la adrespostulon, kaj indas legi ĝin antaŭ ol kulpigi la servilon.
La direktojn fiksas la priskribilo, ne via protokolo. Legadoj donas al vi elirajn pakojn jam elektitajn por la VPN, kaj skribadoj enĵetas enirajn pakojn reen en la interfacon. Via propra transporto ĉifras, aŭtentigas kaj kadrigas tiujn pakojn, ĉar la platformo haltas ĉe la paka limo.
Binder estas unu salto, JNI estas alia akso
Konstruila peto transiras en la sistemon tra Binder, kaj ie sube la platformo vokas denaskan kodon por agordi la aparaton. Temas pri du malsamaj mekanismoj stakigitaj en unu voko. Binder estas interprocesa komunikado inter via aplikaĵo kaj sistema servo, dum JNI estas la ponto de Java al denaska kodo ene de procezo. Legi Android 9 AOSP-arbon igas la stakon aspekti laŭvorte, kaj indas memori, ke internaj klasnomoj kaj dosiervojoj moviĝas inter eldonoj.
Kio ne moviĝas, estas la kontrakto, kiun vi rajtas fidi: la konstruilo, la priskribilo, kiun ĝi redonas, kaj la servo-ciklo ĉirkaŭ ĝi. Neniu aplikaĵo devus voki internan servotransakcion aŭ rekrei aparatnodon por akiri interfacon. Se via kodo dependas de AOSP-klasnomo, ĝi rompiĝos ĉe eldono, kiun vi ne testis.
TUN portas IP, TAP portas Ethernet
TUN estas IP-interfaco. TAP estas Ethernet-interfaco, kaj tiu diferenco decidas, ĉu pontado eĉ eblas. TUN-aparato havas nek MAC-adreson nek Ethernet-kapon por plusendi, do TUN-kliento ne povas aliĝi al tavolo-2-ponto, kiel ajn la itineroj estas skribitaj.
VpnService donas al vi la IP-variaĵon, kiu estas tio, kion VPN ĉe aplikaĵa nivelo bezonas kaj povas subteni sen kruda Ethernet-stako. Se via dezajno postulas pontadon, la platforma vojo ne kondukos vin tien, kaj la root-vojo nur se la kerno kaj la aparataj permesoj kunlaboras.
Kie la root-vojo ankoraŭ venkas
Tri taskoj puŝas homojn for de la platforma vojo. Tavolo-2-pontado bezonas TAP. Kapti trafikon de aplikaĵoj ekster via propra VPN-gamo bezonas videblecon, kiun la kadro ne donas. Politika enkursigado, kiun la konstruilo neniam malkaŝas, ekzemple aparta tabelo por unu celo, bezonas regulojn, kiujn vi mem instalas.
La kostoj alvenas pli poste: malŝlosita bootloader aŭ rootigita bildo, SELinux-politiko, kiu moviĝas sub vi, kaj aparato, kiu ĉesas konduti ĝuste post OTA. Rootigita recepto ankaŭ ne povas esti sendita per normala aplikaĵa disdonado, do plej multaj teamoj tenas ĝin kiel internan diagnozan vojon anstataŭ produkton.
Elekti inter ili
Komencu de la interfaco, kiun vi bezonas, ne de la kodo, kiun vi havas. Se la respondo estas TUN-aparato kun aplikaĵ-limigitaj itineroj, la platforma vojo estas pli mallonga kaj postvivas ĝisdatigojn. Se la respondo implikas Ethernet-kadrojn, kaptadon de aliaj aplikaĵoj aŭ enkursigajn regulojn, kiujn la konstruilo rifuzas, vi estas en root-teritorio kaj devus buĝeti por tio kiel operacian koston.
Kontrolo, kiu solvas plej multajn disputojn: konstruu unue la platforman vojon, registru la rezulton de establish() aparte de la stato de la fora seanco, kaj konfirmu unu pakon en ĉiu direkto. Nur poste decidu, ĉu la mankanta kapablo pravigas forlasi la subtenatan vojon.