Android VPN: deskriptor VpnService versus root tun zařízení
Aplikace pro Android nemohou samy vytvořit tun0: co vrací establish(), kde končí Binder a začíná JNI a kdy vyhraje zařízení vytvořené rootem.
Dvě cesty mohou postavit IP rozhraní před aplikaci Android a obě končí u stejného druhu deskriptoru souboru s velmi rozdílnými vlastníky. Jedna vede přes VpnService a jeho builder, druhá přes root shell a /dev/net/tun. Každé čtení z tohoto deskriptoru je odchozí paket a každý zápis je příchozí. Cesty odděluje to, kdo drží politiku, kdo přežije upgrade operačního systému a kolik směrovací tabulky smíš změnit.
Jdi cestou platformy, pokud nedokážeš pojmenovat konkrétní věc, kterou odmítá udělat. Toto odmítnutí bývá TAP, zachytávání mimo tvou množinu aplikací nebo směrovací pravidlo, které framework nikdy nevystaví.
Na cestě platformy deklaruješ záměr místo příkazů. Dodáš alespoň jednu adresu, trasy, které chceš, servery DNS a volitelný seznam aplikací, a potom systémová služba Androidu provede privilegované nastavení pod vlastní identitou. Název rozhraní se nikdy nedozvíš předem a nikdy nevydáš ioctl, které ho vytváří, a o to jde.
Dva způsoby, jak získat IP rozhraní
Cesta root je starší recept. Otevřeš /dev/net/tun, zavoláš TUNSETIFF s IFF_TUN, zvedneš rozhraní pomocí ip link a adresy i trasy přidáš ručně. Každý krok musíš udělat správně sám, včetně těch, které by framework vyřídil potichu. Jedna chybějící trasa vyvolá u uživatele stejnou zprávu jako mrtvý server.
Ladění se liší stejně jako nastavení. Na cestě root rozhraní buď existuje, nebo ti ioctl vrátil chybu, kterou můžeš přečíst přímo. Na cestě platformy rozhraní existuje jen po úspěchu establish(), takže selhání přijde jako výjimka nebo null deskriptor, ne jako uzel zařízení, do kterého bys mohl šťourat.
Co establish() skutečně vrací
Builder.establish() má tři výsledky a každý potřebuje jinou reakci. Deskriptor znamená, že rozhraní je nakonfigurované a je tvoje k použití, null znamená, že požadavek neprošel, a vyhozená výjimka znamená, že ho systémová služba odmítla. Sloučení null a úspěchu do jednoho booleanu je nejběžnější způsob, jakým aplikace hlásí funkční tunel, který nikdy nenese paket. Referenční příručka k Builderu uvádí požadavek na adresu a stojí za přečtení, než obviníš server.
Směry určuje deskriptor, ne tvůj protokol. Čtení ti dávají odchozí pakety už vybrané pro VPN a zápisy vstřikují příchozí pakety zpět do rozhraní. Pakety šifruje, ověřuje a rámuje tvůj vlastní transport, protože platforma končí na hranici paketu.
Binder je jeden krok, JNI je jiná osa
Požadavek builderu přechází do systému přes Binder a někde pod tím platforma volá nativní kód, aby zařízení nakonfigurovala. To jsou dva různé mechanismy naskládané v jednom volání. Binder je meziprocesní komunikace mezi tvou aplikací a systémovou službou, zatímco JNI je most z Javy do nativního kódu uvnitř procesu. Čtení stromu AOSP pro Android 9 dělá zásobník doslova viditelným a stojí za to pamatovat, že interní názvy tříd a cesty k souborům se mezi verzemi mění.
Co se nemění, je smlouva, na kterou se můžeš spolehnout: builder, deskriptor, který vrací, a životní cyklus služby kolem něj. Žádná aplikace by neměla volat interní transakci služby ani znovu vytvářet uzel zařízení, aby získala rozhraní. Pokud tvůj kód závisí na názvu třídy z AOSP, rozbije se ve verzi, kterou jsi netestoval.
TUN nese IP, TAP nese Ethernet
TUN je IP rozhraní. TAP je Ethernet rozhraní a tento rozdíl rozhoduje, zda je propojení vůbec možné. Zařízení TUN nemá MAC adresu ani Ethernetovou hlavičku k předávání, takže klient TUN se nemůže připojit k mostu vrstvy 2, ať jsou trasy napsané jakkoli.
VpnService ti dává variantu IP, kterou VPN na úrovni aplikace potřebuje a kterou zvládne bez surového Ethernet zásobníku. Jestli tvůj návrh vyžaduje propojení, cesta platformy tě tam nedovede a cesta root jen tehdy, když jádro a oprávnění zařízení spolupracují.
Kde cesta root stále vítězí
Tři úlohy tlačí lidi z cesty platformy. Propojení vrstvy 2 potřebuje TAP. Zachytávání provozu aplikací mimo tvůj rozsah VPN potřebuje viditelnost, kterou framework nedává. Politické směrování, které builder nikdy nevystaví, třeba samostatnou tabulku pro jednu destinaci, potřebuje pravidla, která nainstaluješ sám.
Náklady přijdou později: odemčený bootloader nebo obraz s rootem, politika SELinux, která se pod tebou mění, a zařízení, které se po OTA přestane chovat správně. Recept s rootem také nelze doručit běžnou distribucí aplikací, takže většina týmů ho drží jako interní diagnostickou cestu, ne jako produkt.
Volba mezi nimi
Začni od rozhraní, které potřebuješ, ne od kódu, který máš. Pokud je odpovědí zařízení TUN s trasami omezenými na aplikaci, cesta platformy je kratší a přežije upgrady. Pokud odpověď zahrnuje Ethernetové rámce, zachytávání jiných aplikací nebo směrovací pravidla, která builder odmítá, jsi na území rootu a měl bys na to vyhradit rozpočet jako provozní náklad.
Kontrola, která vyřeší většinu sporů: nejdřív postav cestu platformy, zaznamenej výsledek establish() odděleně od stavu vzdálené relace a potvrď jeden paket v každém směru. Teprve pak rozhodni, zda chybějící schopnost ospravedlňuje opuštění podporované cesty.