Android VPN: VpnService deskriptor nasuprot root tun uređaju
Android aplikacije ne mogu same stvoriti tun0: što establish() vraća, gdje Binder staje i JNI počinje, i kada uređaj koji je stvorio root pobjeđuje.
Dva puta mogu postaviti IP interfejs ispred Android aplikacije, i oba završavaju na istoj vrsti datotečnog deskriptora s vrlo različitim vlasnicima. Jedan ide kroz VpnService i njegov builder, drugi kroz root shell i /dev/net/tun. Svako čitanje s tog deskriptora je odlazni paket, a svaki upis je dolazni. Ono što razdvaja puteve je ko drži politiku, ko preživi nadogradnju OS-a i koliko tabele rutiranja smiješ dotaknuti.
Uzmi put platforme osim ako možeš imenovati konkretnu stvar koju ona odbija uraditi. To odbijanje je obično TAP, hvatanje izvan vlastitog skupa aplikacija ili pravilo rutiranja koje framework nikad ne izlaže.
Na putu platforme izjavljuješ namjeru umjesto komandi. Dostavljaš barem jednu adresu, željene rute, DNS servere i opcionalnu listu aplikacija, a zatim sistemska služba Androida obavlja privilegiranu postavku pod vlastitim identitetom. Nikad ne saznaš ime interfejsa unaprijed i nikad ne izdaš ioctl koji ga stvara, i u tome je poanta.
Dva načina da dobiješ IP interfejs
Root put je stariji recept. Otvoriš /dev/net/tun, pozoveš TUNSETIFF s IFF_TUN, podigneš interfejs pomoću ip link i ručno dodaš adrese i rute. Svaki korak moraš sam ispravno izvesti, uključujući one koje bi framework obavio tiho. Jedna ruta koja nedostaje daje korisniku isti izvještaj kao mrtav server.
Otklanjanje grešaka razlikuje se jednako kao i postavljanje. Na root putu interfejs ili postoji ili je tvoj ioctl vratio grešku koju možeš pročitati direktno. Na putu platforme interfejs postoji samo nakon što establish() uspije, pa neuspjeh dolazi kao izuzetak ili null deskriptor, a ne kao čvor uređaja u koji možeš čačkati.
Šta establish() zaista vraća
Builder.establish() ima tri ishoda i oni traže tri različita odgovora. Deskriptor znači da je interfejs konfigurisan i tvoj za korištenje, null znači da zahtjev nije prošao, a bačeni izuzetak znači da ga je sistemska služba odbila. Spajanje null i uspjeha u jedan boolean najčešći je način na koji aplikacija prijavljuje tunel koji radi, a nikad ne nosi paket. Referenca za Builder navodi zahtjev za adresom, i to vrijedi pročitati prije nego okriviš server.
Smjerove određuje deskriptor, ne tvoj protokol. Čitanja ti daju odlazne pakete koji su već odabrani za VPN, a upisi ubrizgavaju dolazne pakete natrag u interfejs. Tvoj vlastiti transport šifrira, provjerava i uokviruje te pakete, jer platforma staje na granici paketa.
Binder je jedan skok, JNI je druga osa
Zahtjev builder-a prelazi u sistem kroz Binder, a negdje ispod toga platforma poziva nativni kod da konfiguriše uređaj. To su dva različita mehanizma naslagana u jedan poziv. Binder je međuprocesna komunikacija između tvoje aplikacije i sistemske službe, dok je JNI most od Java do nativnog koda unutar procesa. Čitanje Android 9 AOSP stabla čini stog doslovnim, i vrijedi zapamtiti da se interni nazivi klasa i putanje datoteka pomjeraju između izdanja.
Ono što se ne pomjera je ugovor na koji se možeš osloniti: builder, deskriptor koji vraća i životni ciklus službe oko njega. Nijedna aplikacija ne bi trebala zvati internu transakciju službe ni ponovo stvarati čvor uređaja da bi dobila interfejs. Ako tvoj kod zavisi od imena klase iz AOSP-a, slomit će se na izdanju koje nisi testirao.
TUN nosi IP, TAP nosi Ethernet
TUN je IP interfejs. TAP je Ethernet interfejs, i ta razlika odlučuje je li premošćivanje uopće moguće. TUN uređaj nema MAC adresu ni Ethernet zaglavlje za prosljeđivanje, pa se TUN klijent ne može pridružiti mostu sloja 2, kako god rute bile napisane.
VpnService ti daje IP varijantu, koja je ono što VPN na razini aplikacije treba i što može podržati bez sirovog Ethernet stacka. Ako tvoj dizajn zahtijeva premošćivanje, put platforme te neće odvesti tamo, a root put hoće samo ako kernel i dozvole uređaja sarađuju.
Gdje root put još pobjeđuje
Tri posla tjeraju ljude s puta platforme. Premošćivanje sloja 2 treba TAP. Hvatanje prometa aplikacija izvan tvog VPN opsega treba vidljivost koju framework ne daje. Ruting politike koji builder nikad ne izlaže, poput zasebne tabele za jedno odredište, treba pravila koja sam instaliraš.
Troškovi dolaze kasnije: otključan bootloader ili rootovana slika, SELinux politika koja se pomjera pod tobom i uređaj koji prestane da se ponaša ispravno nakon OTA. Rootovan recept se također ne može slati kroz normalnu distribuciju aplikacija, pa većina timova ga drži kao interni dijagnostički put, a ne kao proizvod.
Izbor između njih
Pođi od interfejsa koji ti treba, ne od koda koji imaš. Ako je odgovor TUN uređaj s rutama ograničenim na aplikaciju, put platforme je kraći i preživljava nadogradnje. Ako odgovor uključuje Ethernet okvire, hvatanje drugih aplikacija ili pravila rutiranja koja builder odbija, ti si na root teritoriji i trebaš to budžetirati kao operativni trošak.
Provjera koja rješava većinu rasprava: prvo izgradi put platforme, zabilježi ishod establish() odvojeno od stanja udaljene sesije i potvrdi po jedan paket u svakom smjeru. Tek onda odluči opravdava li nedostajuća mogućnost napuštanje podržanog puta.