Două căi pot pune o interfață IP în fața unei aplicații Android, iar ambele se termină în același tip de descriptor de fișier, cu proprietari foarte diferiți. Una trece prin VpnService și builder-ul său, cealaltă printr-un shell root și /dev/net/tun. Fiecare citire pe acel descriptor este un pachet de ieșire, iar fiecare scriere este un pachet de intrare. Ce desparte căile este cine deține politica, cine supraviețuiește unui upgrade de OS și cât din tabela de rutare ai voie să atingi.

Ia calea platformei dacă nu poți numi lucrul concret pe care îl refuză. Acel refuz este de obicei TAP, captura în afara setului tău de aplicații sau o regulă de rutare pe care framework-ul nu o expune niciodată.

Pe calea platformei declari intenție în loc de comenzi. Furnizezi cel puțin o adresă, rutele dorite, servere DNS și o listă opțională de aplicații, apoi serviciul de sistem al Android face configurația privilegiată sub propria identitate. Nu afli niciodată numele interfeței dinainte și nu emiți niciodată ioctl-ul care o creează, iar acesta este punctul.

Două moduri de a obține o interfață IP

Calea root este rețeta mai veche. Deschizi /dev/net/tun, apelezi TUNSETIFF cu IFF_TUN, ridici interfața cu ip link și adaugi adrese și rute manual. Fiecare pas este al tău de făcut corect, inclusiv cele pe care framework-ul le-ar fi rezolvat în tăcere. O rută lipsă produce același raport pentru utilizator ca un server mort.

Depanarea diferă la fel de mult ca configurarea. Pe calea root interfața fie există, fie ioctl-ul tău a returnat o eroare pe care o poți citi direct. Pe calea platformei interfața există doar după ce establish() reușește, așa că o defecțiune vine ca excepție sau ca descriptor null în loc de nod de dispozitiv în care să scormonești.

Ce returnează de fapt establish()

Builder.establish() are trei rezultate și ele cer trei răspunsuri diferite. Un descriptor înseamnă că interfața este configurată și a ta de folosit, null înseamnă că cererea nu a trecut, iar o excepție aruncată înseamnă că serviciul de sistem a respins-o. A uni null și succesul într-un singur boolean este cel mai comun mod în care o aplicație raportează un tunel funcțional care nu duce niciodată un pachet. Referința Builder indică cerința de adresă și merită citită înainte de a da vina pe server.

Direcțiile sunt fixate de descriptor, nu de protocolul tău. Citirile îți dau pachete de ieșire deja selectate pentru VPN, iar scrierile injectează pachete de intrare înapoi în interfață. Transportul tău propriu criptează, autentifică și încadrează acele pachete, pentru că platforma se oprește la granița pachetului.

Binder este un salt, JNI este o altă axă

O cerere a builder-ului trece în sistem prin Binder, iar undeva dedesubt platforma apelează cod nativ pentru a configura dispozitivul. Sunt două mecanisme diferite stivuite într-un singur apel. Binder este comunicare între procese între aplicația ta și un serviciu de sistem, în timp ce JNI este puntea de la Java la nativ în interiorul unui proces. Citirea unui arbore AOSP de Android 9 face stiva să pară literală și merită reținut că numele interne de clase și căile de fișiere se mută între versiuni.

Ce nu se mișcă este contractul pe care te poți baza: builder-ul, descriptorul pe care îl returnează și ciclul de viață al serviciului din jur. Nicio aplicație nu ar trebui să apeleze o tranzacție internă a serviciului sau să recreeze un nod de dispozitiv pentru a obține o interfață. Dacă codul tău depinde de un nume de clasă AOSP, se va rupe pe o versiune pe care nu ai testat-o.

TUN duce IP, TAP duce Ethernet

TUN este o interfață IP. TAP este o interfață Ethernet, iar această diferență decide dacă puntea este măcar posibilă. Un dispozitiv TUN nu are adresă MAC și nu are antet Ethernet de forwardat, așa că un client TUN nu poate intra într-o punte de nivel 2, oricum ar fi scrise rutele.

VpnService îți dă varianta IP, care este ce are nevoie un VPN la nivel de aplicație și ce poate susține fără o stivă Ethernet brută. Dacă designul tău cere punte, calea platformei nu te duce acolo, iar calea root doar dacă kernelul și permisiunile dispozitivului cooperează.

Unde calea root încă câștigă

Trei sarcini împing oamenii de pe calea platformei. Puntea de nivel 2 are nevoie de TAP. Capturarea traficului din aplicații din afara sferei tale de VPN are nevoie de vizibilitate pe care framework-ul nu o acordă. Rutarea pe politică pe care builder-ul nu o expune niciodată, cum ar fi o tabelă separată pentru o destinație, are nevoie de reguli pe care le instalezi singur.

Costurile vin mai târziu: un bootloader deblocat sau o imagine cu root, o politică SELinux care se mișcă sub tine și un dispozitiv care nu se mai comportă corect după o OTA. O rețetă cu root nu poate fi livrată nici prin distribuția normală de aplicații, așa că majoritatea echipelor o păstrează ca o cale de diagnostic intern, nu ca produs.

Alegerea între ele

Pornește de la interfața de care ai nevoie, nu de la codul pe care îl ai. Dacă răspunsul este un dispozitiv TUN cu rute limitate la aplicație, calea platformei este mai scurtă și supraviețuiește actualizărilor. Dacă răspunsul implică cadre Ethernet, captura altor aplicații sau reguli de rutare pe care builder-ul le respinge, ești pe teritoriul root și ar trebui să bugetezi asta ca un cost operațional.

O verificare care rezolvă majoritatea discuțiilor: construiește mai întâi calea platformei, notează rezultatul establish() separat de starea sesiunii la distanță și confirmă un pachet în fiecare direcție. Abia apoi decide dacă abilitatea lipsă justifică părăsirea căii suportate.

Articole similare