Attribuzione VPN su Android: getConnectionOwnerUid contro /proc/net
Android 10 nasconde /proc/net alle app. Una VPN riceve ancora pacchetti, ma l'attribuzione passa a getConnectionOwnerUid, con le proprie regole di tuple e permessi.
Su Android 10 e successivi, un'app che apre /proc/net/tcp riceve un rifiuto di accesso invece di una tabella di socket. L'interfaccia VPN non se ne cura: i pacchetti arrivano comunque, perché il tunnel è una decisione di routing, non un permesso di file. Ciò che è cambiato è la risposta a una domanda più stretta: «quale applicazione possiede questa connessione?» Il sostituto è ConnectivityManager.getConnectionOwnerUid, la query per connessione supportata, e la scelta tra la vecchia abitudine e la nuova API dipende da quale dei tre compiti hai davvero: ricevere pacchetti, attribuire un flusso o sommare l'utilizzo.
Due modi per rispondere alla domanda del proprietario
Prima dell'API 29, un processo privilegiato o root poteva leggere le tabelle di socket del kernel, far corrispondere la quadrupla e conoscere l'UID proprietario. Questo dava un inventario dell'intero dispositivo in un solo file. Dall'API 29 la vista del filesystem è limitata, e la via supportata è una query per connessione con il protocollo, l'indirizzo locale e l'indirizzo remoto.
| 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 |
Il confronto non è «vecchio contro nuovo». Sono tre compiti diversi che un tempo erano soddisfatti da un solo file leggibile e ora sono soddisfatti da tre interfacce diverse con tre contratti diversi.
L'indirizzo locale è l'estremità dell'app
La firma del metodo prende un protocollo, un indirizzo di socket locale e un indirizzo di socket remoto. «Locale» significa il lato dell'app nella connessione, non l'estremità che al momento dell'ispezione risulta essere l'origine del pacchetto. Un pacchetto che viaggia dal server remoto verso l'app ha origine e destinazione invertite rispetto a quella definizione.
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
int uid = cm.getConnectionOwnerUid(
OsConstants.IPPROTO_TCP, appLocalAddress, appRemoteAddress);Questo errore di direzione è la ragione più comune per cui una query funzionante restituisce la risposta sbagliata, o nessuna. Registra entrambe le estremità prima della chiamata e conferma la coppia rispetto alla connessione che l'app ha realmente aperto. Anche la famiglia di indirizzi conta: un indirizzo mappato IPv4 e un indirizzo IPv6 nativo sono tuple diverse, così come i flussi TCP e UDP sulle stesse porte.
Autorizzazione e risultati sconosciuti fanno parte del contratto
La query è riservata ai chiamanti autorizzati, inclusa l'app VPN attiva per l'utente interessato. Un'app di monitoraggio estranea non può presumere di poter enumerare le connessioni di un altro profilo, e una chiamata non autorizzata può lanciare un'eccezione di sicurezza invece di restituire un errore cortese. Gestisci entrambi gli esiti in modo esplicito.
Un proprietario assente non è un nome di pacchetto. Il flusso può essersi chiuso tra il pacchetto e la query, la tupla può essere incompleta, o al chiamante può semplicemente non essere permesso saperlo. Mettere in cache la prima risposta riuscita e riusarla dopo attribuisce una nuova connessione a un'app vecchia, il che è peggio che segnalare sconosciuto.
Quando l'attribuzione fallisce, il traffico ha comunque bisogno di una decisione: bloccarlo dove la policy lo richiede, consentire solo un ambito che comprendi, o esporre la lacuna. Quel fallback appartiene al progetto, non a un commento.
La contabilità è un'altra domanda
NetworkStatsManager riporta l'utilizzo secondo le proprie regole di accesso. Può dirti quanto ha trasferito un UID in un periodo; non può dirti il proprietario di un pacchetto vivo arbitrario. Le capacità di rete descrivono proprietà di una rete, non una directory di socket. Mescolare le tre cose produce dashboard che sembrano coerenti e non lo sono.
Per il caso semplice di decidere quali app usano il tunnel, gli elenchi supportati di app consentite e vietate di Android esprimono già la policy senza estrarre nulla. Riserva l'attribuzione per flusso ai casi che ne hanno davvero bisogno, e metti in budget un'implementazione mantenuta che tratti direzione, permessi e stati sconosciuti insieme.
Scegli lo strumento in base al compito, poi prova i bordi
Se il requisito è la selezione del tunnel, usa l'elenco della piattaforma. Se è attribuzione dal vivo, usa la query del proprietario e tratta ogni tupla senza risposta come sconosciuta. Se sono totali storici, usa l'API delle statistiche. Poi prova i tre bordi che rompono le implementazioni ingenue: un pacchetto in direzione inversa, un flusso che si chiude prima della query e un chiamante che non è la VPN attiva.
Niente di tutto questo richiede di disattivare SELinux, fare il root del telefono o rendere le tabelle del kernel leggibili globalmente. Il vincolo è il punto: la piattaforma ha spostato l'attribuzione dietro un contratto così che la risposta sia disponibile per il componente che ne ha legittimamente bisogno, e per nessun altro.