To vegan kan setje eit IP-grensesnitt framfor ein Android-app, og begge endar i same slags fildeskriptor med svært ulike eigarar. Den eine går gjennom VpnService og byggjaren, den andre gjennom eit root-skal og /dev/net/tun. Kvar lesing på den deskriptoren er ein utgåande pakke, og kvar skriving er ein inngåande. Det som skil vegane er kven som held policyen, kven som overlever ei OS-oppgradering, og kor mykje av rutetabellen du får røre.

Ta plattformvegen med mindre du kan namnsetje det konkrete han nektar å gjere. Den nektninga er vanlegvis TAP, fangst utanfor ditt eige app-sett, eller ei rutingsregel som rammeverket aldri eksponerer.

På plattformvegen erklærer du hensikt i staden for kommandoar. Du oppgir minst éi adresse, rutene du vil ha, DNS-tenarar og ei valfri app-liste, og deretter utfører systemtenesta til Android det privilegerte oppsettet under sin eigen identitet. Du lærer aldri grensesnittnamnet på førehand, og du sender aldri ioctl-en som lagar det, og det er poenget.

To måtar å få eit IP-grensesnitt

Root-vegen er den eldre oppskrifta. Du opnar /dev/net/tun, kallar TUNSETIFF med IFF_TUN, tek grensesnittet opp med ip link, og legg til adresser og ruter for hand. Kvart steg er ditt å få rett, også dei rammeverket ville ha handsama i stillheit. Éi manglande rute gir same brukarrapport som ein daud tenar.

Feilsøking skil seg like mykje som oppsett. På root-vegen finst grensesnittet anten, eller så returnerte ioctl-en din ein feil du kan lese direkte. På plattformvegen finst grensesnittet først etter at establish() lukkast, så ein feil kjem som eit unntak eller ein null-deskriptor i staden for ein einingnode du kan pirke i.

Kva establish() faktisk returnerer

Builder.establish() har tre utfall, og dei krev tre ulike svar. Ein deskriptor betyr at grensesnittet er konfigurert og ditt å bruke, null betyr at førespurnaden ikkje gjekk gjennom, og eit kasta unntak betyr at systemtenesta avviste han. Å slå null og suksess saman til éin boolean er den vanlegaste måten ein app rapporterer om ein fungerande tunnel som aldri ber ein pakke. Builder-referansen oppgir adressekravet, og det er verdt å lese før du klandrar tenaren.

Retningane blir bestemte av deskriptoren, ikkje av protokollen din. Lesingar gir deg utgåande pakkar som allereie er valde ut for VPN-et, og skrivingar injiserer inngåande pakkar tilbake i grensesnittet. Din eigen transport krypterer, autentiserer og rammar inn desse pakkane, fordi plattformen stoppar ved pakkegrensa.

Binder er eitt hopp, JNI er ein annan akse

Ein byggjarførespurnad går inn i systemet gjennom Binder, og ein stad under der kallar plattformen inn native kode for å konfigurere eininga. Det er to ulike mekanismar stabla inne i eitt kall. Binder er prosesskommunikasjon mellom appen din og ei systemteneste, medan JNI er brua frå Java til native kode inne i ein prosess. Å lese eit Android 9 AOSP-tre får stakken til å sjå bokstaveleg ut, og det er verdt å hugse at interne klassenamn og filstiar flyttar seg mellom utgåver.

Det som ikkje flyttar seg, er kontrakten du kan stole på: byggjaren, deskriptoren han returnerer, og livssyklusen til tenesta rundt han. Ingen app bør kalle ein intern tenestetransaksjon eller gjenskape ein einingnode for å få eit grensesnitt. Viss koden din er avhengig av eit AOSP-klassenamn, vil han knekke på ei utgåve du ikkje har testa.

TUN ber IP, TAP ber Ethernet

TUN er eit IP-grensesnitt. TAP er eit Ethernet-grensesnitt, og den skilnaden avgjer om bruing i det heile er mogleg. Ei TUN-eining har inga MAC-adresse og ingen Ethernet-header å videresende, så ein TUN-klient kan ikkje bli med i ei lag-2-bru, uansett korleis rutene er skrivne.

VpnService gir deg IP-varianten, som er det eit VPN på app-nivå treng og kan støtte utan ein rå Ethernet-stakk. Viss designet ditt krev bruing, fører ikkje plattformvegen deg dit, og root-vegen gjer det berre viss kjernen og løyva til eininga samarbeider.

Kor root-vegen framleis vinn

Tre jobbar skyv folk bort frå plattformvegen. Lag-2-bruing treng TAP. Fangst av trafikk frå apper utanfor ditt eige VPN-område treng sikt som rammeverket ikkje gir. Policy-ruting som byggjaren aldri eksponerer, som ein eigen tabell for éin destinasjon, treng reglar du installerer sjølv.

Kostnadene kjem seinare: ein opplåst bootloader eller eit roota image, SELinux-policy som flyttar seg under deg, og ei eining som sluttar å oppføre seg rett etter ein OTA. Ei roota oppskrift kan heller ikkje sendast ut gjennom normal app-distribusjon, så dei fleste team held ho som ein intern diagnostisk veg i staden for eit produkt.

Valet mellom dei

Byrja med grensesnittet du treng, ikkje koden du har. Viss svaret er ei TUN-eining med app-avgrensa ruter, er plattformvegen kortare og overlever oppgraderingar. Viss svaret involverer Ethernet-rammer, fangst av andre apper eller rutingsreglar byggjaren avviser, er du på root-territorium og bør budsjettere for det som ein driftskostnad.

Ein sjekk som avgjer dei fleste diskusjonar: bygg plattformvegen først, registrer establish()-resultatet skilt frå tilstanden til fjernøkta, og stadfest éin pakke i kvar retning. Først då avgjer du om den manglande evna rettferdiggjer å forlate den støtta vegen.

Relaterte artiklar