Android VPN-attribuering: getConnectionOwnerUid mot /proc/net
Android 10 döljer /proc/net för appar. En VPN får fortfarande paket, men attribueringen flyttar till getConnectionOwnerUid, med egna regler för tuple och behörigheter.
På Android 10 och senare får en app som öppnar /proc/net/tcp åtkomst nekad i stället för en sockettabell. VPN-gränssnittet bryr sig inte: paketen kommer fortfarande fram, eftersom tunneln är ett routingbeslut, inte en filbehörighet. Det som ändrades är svaret på en snävare fråga: ”vilken app äger den här anslutningen?” Ersättningen är ConnectivityManager.getConnectionOwnerUid, den stödda frågan per anslutning, och valet mellan den gamla vanan och det nya API:et beror på vilken av tre uppgifter du faktiskt har: ta emot paket, attribuera ett flöde eller summera användning.
Två sätt att svara på ägarfrågan
Före API 29 kunde en privilegierad process eller root läsa kärnans sockettabeller, matcha fyrtupeln och ta reda på den ägande UID:n. Det gav en enhetsövergripande inventering i en enda fil. Från API 29 är filsystemsvyn begränsad, och den stödda vägen är en fråga per anslutning med protokollet, den lokala adressen och den externa 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 |
Jämförelsen är inte ”gammalt mot nytt”. Det är tre olika uppgifter som tidigare löstes av en enda läsbar fil och nu löses av tre olika gränssnitt med tre olika kontrakt.
Den lokala adressen är appens ändpunkt
Metodsignaturen tar ett protokoll, en lokal socketadress och en extern socketadress. ”Lokal” betyder appens sida av anslutningen, inte den ände som råkar vara paketets källa i det ögonblick du undersöker det. Ett paket som färdas från den externa servern tillbaka till appen har källa och mål ombytta i förhållande till den definitionen.
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
int uid = cm.getConnectionOwnerUid(
OsConstants.IPPROTO_TCP, appLocalAddress, appRemoteAddress);Det här riktningsfelet är den vanligaste orsaken till att en fungerande fråga ger fel svar, eller inget. Logga båda ändpunkterna före anropet och bekräfta paret mot den anslutning appen faktiskt öppnade. Adressfamiljen spelar också roll: en IPv4-mappad adress och en inbyggd IPv6-adress är olika tupler, liksom TCP- och UDP-flöden på samma portar.
Auktorisering och okända resultat är en del av kontraktet
Frågan är begränsad till behöriga anropare, inklusive den aktiva VPN-appen för den berörda användaren. En orelaterad övervakningsapp kan inte utgå från att den kan räkna upp en annan profils anslutningar, och ett obehörigt anrop kan kasta ett säkerhetsundantag i stället för att returnera ett vänligt fel. Hantera båda utfallen explicit.
En saknad ägare är inte ett paketnamn. Flödet kan ha stängts mellan paketet och frågan, tupeln kan vara ofullständig, eller anroparen kan helt enkelt inte ha rätt att veta. Att cacha det första lyckade svaret och återanvända det senare tillskriver en ny anslutning en gammal app, vilket är värre än att rapportera okänt.
När attribueringen misslyckas behöver trafiken ändå ett beslut: blockera den där policyn kräver det, tillåt bara en omfattning du förstår, eller synliggör luckan. Den fallbacken hör till designen, inte till en kommentar.
Redovisning är en annan fråga
NetworkStatsManager rapporterar användning enligt sina egna åtkomstregler. Det kan säga hur mycket en UID överförde under en period; det kan inte säga ägaren till ett godtyckligt levande paket. Nätverksfunktioner beskriver ett nätverks egenskaper, inte en katalog över sockets. Att blanda de tre ger dashboards som ser konsekventa ut och inte är det.
För det enkla fallet att avgöra vilka appar som använder tunneln uttrycker Androids stödda listor över tillåtna och förbjudna appar redan policyn utan att skrapa något. Reservera attribuering per flöde för de fall som verkligen behöver den, och budgetera för en underhållen implementering som hanterar riktning, behörigheter och okända tillstånd tillsammans.
Välj verktyg efter uppgift, testa sedan kanterna
Om kravet är tunnval, använd plattformens lista. Om det är live-attribuering, använd ägarfrågan och behandla varje obesvärlig tuple som okänd. Om det är historiska totaler, använd stats-API:et. Testa sedan de tre kanter som knäcker naiva implementeringar: ett paket i motsatt riktning, ett flöde som stängs innan du frågar, och en anropare som inte är den aktiva VPN:en.
Inget av detta kräver att du inaktiverar SELinux, rootar telefonen eller gör kärntabeller globalt läsbara. Begränsningen är poängen: plattformen flyttade attribueringen bakom ett kontrakt så att svaret finns tillgängligt för den komponent som legitimt behöver det, och för ingen annan.