Atribuirea VPN pe Android: getConnectionOwnerUid versus /proc/net
Android 10 ascunde /proc/net de aplicații. Un VPN primește în continuare pachete, dar atribuirea trece la getConnectionOwnerUid, cu propriile reguli de tuple și permisiuni.
Pe Android 10 și versiunile ulterioare, o aplicație care deschide /proc/net/tcp primește un refuz de acces în locul unui tabel de socketuri. Interfața VPN nu este deranjată: pachetele sosesc în continuare, pentru că tunelul este o decizie de rutare, nu o permisiune de fișier. Ce s-a schimbat este răspunsul la o întrebare mai îngustă: „care aplicație deține această conexiune?” Înlocuitorul este ConnectivityManager.getConnectionOwnerUid, interogarea suportată pentru fiecare conexiune, iar alegerea între obiceiul vechi și noul API depinde de care dintre cele trei sarcini o ai de fapt: recepția pachetelor, atribuirea unui flux sau însumarea consumului.
Două moduri de a răspunde la întrebarea despre proprietar
Înainte de API 29, un proces privilegiat sau root putea citi tabelele de socketuri ale nucleului, potrivi cvadruplul și afla UID-ul proprietarului. Asta oferea un inventar la nivel de dispozitiv într-un singur fișier. De la API 29, vizualizarea sistemului de fișiere este restricționată, iar calea suportată este o interogare pentru fiecare conexiune cu protocolul, adresa locală și adresa la distanță.
| Question | Legacy socket tables | getConnectionOwnerUid |
|---|---|---|
| Who owns this flow? | Scan and match the tuple | Ask once per tuple |
| Device-wide inventory? | Yes, when readable | No, one flow at a time |
| Historical usage? | No | No; that is NetworkStatsManager |
| Works without root? | No on modern releases | Yes, for authorized callers |
Comparația nu este „vechi contra nou”. Sunt trei sarcini diferite care înainte erau satisfăcute de un singur fișier lizibil, iar acum sunt satisfăcute de trei interfețe diferite cu trei contracte diferite.
Adresa locală este capătul aplicației
Semnătura metodei primește un protocol, o adresă de socket locală și o adresă de socket la distanță. „Locală” înseamnă partea aplicației din conexiune, nu capătul care în momentul inspecției se întâmplă să fie sursa pachetului. Un pachet care călătorește de la serverul de la distanță înapoi la aplicație are sursa și destinația inversate față de această definiție.
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
int uid = cm.getConnectionOwnerUid(
OsConstants.IPPROTO_TCP, appLocalAddress, appRemoteAddress);Această greșeală de direcție este cauza cea mai frecventă pentru care o interogare funcțională returnează răspunsul greșit sau niciunul. Înregistrează ambele capete înainte de apel și confirmă perechea față de conexiunea pe care aplicația a deschis-o efectiv. Și familia de adrese contează: o adresă mapată prin IPv4 și o adresă IPv6 nativă sunt tuple diferite, la fel ca fluxurile TCP și UDP pe aceleași porturi.
Autorizarea și rezultatele necunoscute fac parte din contract
Interogarea este limitată la apelanți autorizați, inclusiv aplicația VPN activă pentru utilizatorul respectiv. O aplicație de monitorizare nelegată nu poate presupune că poate enumera conexiunile altui profil, iar un apel neautorizat poate arunca o excepție de securitate în loc să returneze o eroare prietenoasă. Tratează ambele rezultate explicit.
Un proprietar lipsă nu este un nume de pachet. Fluxul s-ar putea să se fi închis între pachet și interogare, tuplul poate fi incomplet sau pur și simplu nu i se permite apelantului să știe. Dacă memorezi primul răspuns reușit și îl refolosești mai târziu, atribui o conexiune nouă unei aplicații vechi, ceea ce este mai rău decât să raportezi necunoscut.
Când atribuirea eșuează, traficul are totuși nevoie de o decizie: blochează-l acolo unde politica o cere, permite doar un domeniu pe care îl înțelegi sau expune golul. Acea soluție de rezervă aparține designului, nu unui comentariu.
Contabilitatea este o altă întrebare
NetworkStatsManager raportează consumul conform propriilor reguli de acces. Poate spune cât a transferat un UID într-o perioadă; nu poate spune proprietarul unui pachet viu arbitrar. Capabilitățile de rețea descriu proprietățile unei rețele, nu un director de socketuri. Amestecarea celor trei produce tablouri de bord care par consecvente și nu sunt.
Pentru cazul simplu de a decide ce aplicații folosesc tunelul, listele suportate de aplicații permise și interzise din Android exprimă deja politica fără nicio extragere. Păstrează atribuirea pe flux pentru cazurile care chiar au nevoie de ea și bugetează o implementare întreținută care tratează împreună direcția, permisiunile și stările necunoscute.
Alege instrumentul după sarcină, apoi testează marginile
Dacă cerința este selecția tunelului, folosește lista platformei. Dacă este atribuire în timp real, folosește interogarea proprietarului și tratează fiecare tuplu fără răspuns ca necunoscut. Dacă sunt totaluri istorice, folosește stats API. Apoi testează cele trei margini care sparg implementările naive: un pachet în direcție inversă, un flux care se închide înainte de interogare și un apelant care nu este VPN-ul activ.
Nimic din toate acestea nu necesită dezactivarea SELinux, rootarea telefonului sau facerea tabelelor nucleului lizibile global. Restricția este ideea: platforma a mutat atribuirea în spatele unui contract, astfel încât răspunsul să fie disponibil componentei care are nevoie de el în mod legitim și nimănui altcuiva.