Android VPN-attribusjon: getConnectionOwnerUid mot /proc/net
Android 10 skjuler /proc/net for apper. En VPN får fortsatt pakker, men attribusjonen flytter til getConnectionOwnerUid, med egne tuple- og tillatelsesregler.
På Android 10 og nyere får en app som åpner /proc/net/tcp avslag på tilgang i stedet for en socket-tabell. VPN-grensesnittet bryr seg ikke: pakkene kommer fortsatt, fordi tunnelen er en rutingsbeslutning, ikke en filrettighet. Det som endret seg, er svaret på et smalere spørsmål: «hvilken app eier denne tilkoblingen?» Erstatningen er ConnectivityManager.getConnectionOwnerUid, det støttede per-tilkobling-oppslaget, og valget mellom den gamle vanen og det nye API-et avhenger av hvilken av tre oppgaver du faktisk har: motta pakker, attribuere en strøm eller summere bruk.
To måter å svare på eierspørsmålet
Før API 29 kunne en privilegert prosess eller root lese kjernens socket-tabeller, matche firedobbelen og finne den eierende UID-en. Det ga en enhetsomfattende oversikt i én fil. Fra API 29 er filsystemvisningen begrenset, og den støttede veien er et oppslag per tilkobling med protokollen, den lokale adressen og den eksterne adressen.
| 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 |
Sammenligningen er ikke «gammelt mot nytt». Det er tre forskjellige oppgaver som før ble dekket av én lesbar fil og nå dekkes av tre forskjellige grensesnitt med tre forskjellige kontrakter.
Den lokale adressen er appens endepunkt
Metodesignaturen tar en protokoll, en lokal socket-adresse og en ekstern socket-adresse. «Lokal» betyr appens side av tilkoblingen, ikke endepunktet som tilfeldigvis er pakkens kilde i det øyeblikket du undersøker den. En pakke som reiser fra den eksterne serveren tilbake til appen, har kilde og mål byttet om i forhold til den definisjonen.
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
int uid = cm.getConnectionOwnerUid(
OsConstants.IPPROTO_TCP, appLocalAddress, appRemoteAddress);Denne retningsfeilen er den vanligste årsaken til at et fungerende oppslag gir feil svar, eller ingen. Logg begge endepunktene før kallet og bekreft paret mot tilkoblingen appen faktisk åpnet. Adressefamilien betyr også noe: en IPv4-kartlagt adresse og en nativ IPv6-adresse er forskjellige tupler, og det samme er TCP- og UDP-strømmer på de samme portene.
Autorisasjon og ukjente resultater er en del av kontrakten
Oppslaget er begrenset til autoriserte kallere, inkludert den aktive VPN-appen for den aktuelle brukeren. En urelatert overvåkingsapp kan ikke anta at den kan liste opp en annen profils tilkoblinger, og et uautorisert kall kan kaste en sikkerhetsunntak i stedet for å returnere en vennlig feil. Håndter begge utfallene eksplisitt.
En manglende eier er ikke et pakkenavn. Strømmen kan ha lukket seg mellom pakken og oppslaget, tuple-en kan være ufullstendig, eller kalleren har rett og slett ikke lov til å vite. Å cache det første vellykkede svaret og gjenbruke det senere tilskriver en ny tilkobling en gammel app, noe som er verre enn å rapportere ukjent.
Når attribusjonen feiler, trenger trafikken fortsatt en beslutning: blokker den der policyen krever det, tillat bare et omfang du forstår, eller synliggjør hullet. Den fallbacken hører til designet, ikke til en kommentar.
Regnskap er et annet spørsmål
NetworkStatsManager rapporterer bruk etter sine egne tilgangsregler. Det kan fortelle hvor mye en UID overførte over en periode; det kan ikke fortelle eieren av en vilkårlig levende pakke. Nettverksfunksjoner beskriver egenskapene til et nettverk, ikke en katalog over sockets. Å blande de tre gir dashbord som ser konsistente ut og ikke er det.
For det enkle tilfellet å avgjøre hvilke apper som bruker tunnelen, uttrykker Androids støttede lister over tillatte og forbudte apper allerede policyen uten å skrape noe. Reserver attribusjon per strøm til tilfellene som virkelig trenger det, og budsjetter med en vedlikeholdt implementering som håndterer retning, tillatelser og ukjente tilstander samlet.
Velg verktøyet etter oppgave, og test deretter kantene
Hvis kravet er tunnellvalg, bruk plattformlisten. Hvis det er live-attribusjon, bruk eieroppslaget og behandle hver ubesvarlige tuple som ukjent. Hvis det er historiske totaler, bruk stats-API-et. Test deretter de tre kantene som knekker naive implementeringer: en pakke i motsatt retning, en strøm som lukkes før du spør, og en kaller som ikke er den aktive VPN-en.
Ingenting av dette krever å deaktivere SELinux, roote telefonen eller gjøre kjernetabeller globalt lesbare. Begrensningen er poenget: plattformen flyttet attribusjonen bak en kontrakt slik at svaret er tilgjengelig for komponenten som legitimt trenger det, og for ingen andre.