To veje kan sætte en IP-grænseflade foran en Android-app, og begge ender i samme slags fildeskriptor med meget forskellige ejere. Den ene går gennem VpnService og dens builder, den anden gennem en root-shell og /dev/net/tun. Hver læsning på den deskriptor er en udgående pakke, og hver skrivning er en indgående. Det, der adskiller vejene, er hvem der har politikken, hvem der overlever en OS-opgradering, og hvor meget af rutetabellen du må røre.

Tag platformvejen, medmindre du kan navngive det konkrete, den nægter at gøre. Den afvisning er normalt TAP, optagelse uden for dit eget app-sæt eller en routingregel, som frameworket aldrig blotlægger.

På platformvejen erklærer du hensigt i stedet for kommandoer. Du angiver mindst én adresse, de ruter du vil have, DNS-servere og en valgfri app-liste, og derefter udfører Androids systemtjeneste den privilegerede opsætning under sin egen identitet. Du lærer aldrig grænsefladens navn på forhånd, og du udsteder aldrig det ioctl, der opretter den, og det er pointen.

To måder at få en IP-grænseflade

Root-vejen er den ældre opskrift. Du åbner /dev/net/tun, kalder TUNSETIFF med IFF_TUN, bringer grænsefladen op med ip link og tilføjer adresser og ruter i hånden. Hvert skridt er dit at få rigtigt, også dem frameworket ville have klaret i stilhed. Én manglende rute giver samme brugerrapport som en død server.

Fejlfinding adskiller sig lige så meget som opsætning. På root-vejen findes grænsefladen enten, eller også returnerede dit ioctl en fejl, du kan læse direkte. På platformvejen findes grænsefladen først, efter establish() lykkes, så en fejl kommer som en undtagelse eller en null-deskriptor i stedet for en enhedsknude, du kan rode i.

Hvad establish() faktisk returnerer

Builder.establish() har tre udfald, og de kræver tre forskellige svar. En deskriptor betyder, at grænsefladen er konfigureret og din at bruge, null betyder, at anmodningen ikke gik igennem, og en kastet undtagelse betyder, at systemtjenesten afviste den. At slå null og succes sammen til én boolean er den mest almindelige måde, en app rapporterer en fungerende tunnel, der aldrig bærer en pakke. Builder-referencen angiver adressekravet, og det er værd at læse, før du bebrejder serveren.

Retningerne fastlægges af deskriptoren, ikke af din protokol. Læsninger giver dig udgående pakker, der allerede er udvalgt til VPN'et, og skrivninger injicerer indgående pakker tilbage i grænsefladen. Din egen transport krypterer, autentificerer og indrammer de pakker, fordi platformen stopper ved pakkegrænsen.

Binder er et hop, JNI er en anden akse

En builder-anmodning krydser ind i systemet gennem Binder, og et sted derunder kalder platformen native kode for at konfigurere enheden. Det er to forskellige mekanismer stablet inde i ét kald. Binder er proceskommunikation mellem din app og en systemtjeneste, mens JNI er broen fra Java til native kode inde i en proces. At læse et Android 9 AOSP-træ får stakken til at se bogstavelig ud, og det er værd at huske, at interne klassenavne og filstier flytter sig mellem udgivelser.

Det, der ikke flytter sig, er den kontrakt, du må stole på: builderen, deskriptoren den returnerer, og tjenestens livscyklus omkring den. Ingen app bør kalde en intern tjenestetransaktion eller genskabe en enhedsknude for at få en grænseflade. Hvis din kode afhænger af et AOSP-klassenavn, brækker den på en udgivelse, du ikke har testet.

TUN bærer IP, TAP bærer Ethernet

TUN er en IP-grænseflade. TAP er en Ethernet-grænseflade, og den forskel afgør, om broing overhovedet er muligt. En TUN-enhed har ingen MAC-adresse og ingen Ethernet-header at videresende, så en TUN-klient kan ikke slutte sig til en lag-2-bro, uanset hvordan ruterne er skrevet.

VpnService giver dig IP-varianten, som er det, et VPN på app-niveau har brug for, og det, det kan understøtte uden en rå Ethernet-stak. Hvis dit design kræver broing, fører platformvejen dig ikke derhen, og root-vejen gør det kun, hvis kernen og enhedens tilladelser samarbejder.

Hvor root-vejen stadig vinder

Tre opgaver skubber folk væk fra platformvejen. Lag-2-broing kræver TAP. Optagelse af trafik fra apps uden for dit eget VPN-område kræver synlighed, som frameworket ikke giver. Politikrouting, som builderen aldrig blotlægger, fx en separat tabel til én destination, kræver regler, du selv installerer.

Omkostningerne kommer senere: en oplåst bootloader eller et rootet image, SELinux-politik, der flytter sig under dig, og en enhed, der holder op med at opføre sig ordentligt efter en OTA. En rootet opskrift kan heller ikke sendes ud gennem normal app-distribution, så de fleste teams beholder den som en intern diagnostisk vej i stedet for et produkt.

Valget mellem dem

Begynd med den grænseflade, du har brug for, ikke med den kode, du har. Hvis svaret er en TUN-enhed med app-afgrænsede ruter, er platformvejen kortere og overlever opgraderinger. Hvis svaret involverer Ethernet-frames, optagelse af andre apps eller routingregler, som builderen afviser, er du på root-territorium og bør budgettere med det som en driftsomkostning.

En kontrol, der afgør de fleste diskussioner: byg platformvejen først, notér establish()-resultatet adskilt fra fjernsessionens tilstand, og bekræft én pakke i hver retning. Først derefter afgør du, om den manglende funktion retfærdiggør at forlade den understøttede vej.

Relaterede artikler