Atrybucja VPN w Androidzie: getConnectionOwnerUid kontra /proc/net
Android 10 ukrywa /proc/net przed aplikacjami. VPN nadal odbiera pakiety, ale atrybucja przechodzi do getConnectionOwnerUid, z własnymi regułami tuple i uprawnień.
W Androidzie 10 i nowszych aplikacja, która otwiera /proc/net/tcp, dostaje odmowę dostępu zamiast tablicy socketów. Interfejsowi VPN to nie przeszkadza: pakiety nadal przychodzą, bo tunel to decyzja o routingu, a nie uprawnienie do pliku. Zmieniła się odpowiedź na węższe pytanie: „która aplikacja jest właścicielem tego połączenia?” Zamiennikiem jest ConnectivityManager.getConnectionOwnerUid, obsługiwane zapytanie dla połączenia, a wybór między starym nawykiem a nowym API zależy od tego, które z trzech zadań naprawdę masz: odbieranie pakietów, atrybucja przepływu czy sumowanie użycia.
Dwa sposoby odpowiedzi na pytanie o właściciela
Przed API 29 uprzywilejowany lub root proces mógł czytać tablice socketów jądra, dopasować czwórkę i poznać UID właściciela. Dawało to inwentarz całego urządzenia w jednym pliku. Od API 29 widok systemu plików jest ograniczony, a obsługiwaną drogą jest zapytanie dla połączenia z protokołem, adresem lokalnym i adresem zdalnym.
| 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 |
Porównanie nie brzmi „stare kontra nowe”. To trzy różne zadania, które kiedyś spełniał jeden czytelny plik, a dziś spełniają trzy różne interfejsy z trzema różnymi kontraktami.
Adres lokalny to koniec aplikacji
Sygnatura metody przyjmuje protokół, lokalny adres socketa i zdalny adres socketa. „Lokalny” oznacza stronę aplikacji w połączeniu, a nie ten koniec, który w chwili sprawdzania przypadkiem jest źródłem pakietu. Pakiet wędrujący ze zdalnego serwera z powrotem do aplikacji ma źródło i cel odwrócone względem tej definicji.
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
int uid = cm.getConnectionOwnerUid(
OsConstants.IPPROTO_TCP, appLocalAddress, appRemoteAddress);Ten błąd kierunku jest najczęstszą przyczyną, dla której działające zapytanie zwraca złą odpowiedź albo żadnej. Zarejestruj oba końce przed wywołaniem i potwierdź parę wobec połączenia, które aplikacja naprawdę otworzyła. Rodzina adresów też ma znaczenie: adres mapowany przez IPv4 i natywny adres IPv6 to różne tuple, podobnie jak przepływy TCP i UDP na tych samych portach.
Autoryzacja i nieznane wyniki są częścią kontraktu
Zapytanie jest ograniczone do uprawnionych wywołujących, w tym aktywnej aplikacji VPN dla danego użytkownika. Niepowiązana aplikacja monitorująca nie może zakładać, że wyliczy połączenia innego profilu, a nieautoryzowane wywołanie może rzucić wyjątek bezpieczeństwa zamiast zwrócić przyjazny błąd. Obsłuż oba wyniki jawnie.
Brakujący właściciel to nie nazwa pakietu. Przepływ mógł się zamknąć między pakietem a zapytaniem, tuple może być niepełne albo wywołującemu po prostu nie wolno wiedzieć. Buforowanie pierwszej udanej odpowiedzi i późniejsze jej użycie przypisuje nowe połączenie starej aplikacji, co jest gorsze niż zgłoszenie nieznanego.
Gdy atrybucja zawiedzie, ruch nadal potrzebuje decyzji: zablokuj go tam, gdzie wymaga tego polityka, zezwól tylko na zakres, który rozumiesz, albo pokaż lukę. Ten fallback należy do projektu, nie do komentarza.
Rozliczanie to inne pytanie
NetworkStatsManager raportuje użycie według własnych reguł dostępu. Może powiedzieć, ile UID przesłał w danym okresie; nie może powiedzieć, kto jest właścicielem dowolnego żywego pakietu. Możliwości sieci opisują właściwości sieci, a nie katalog socketów. Mieszanie tych trzech daje pulpity, które wyglądają spójnie i takie nie są.
W prostym przypadku ustalenia, które aplikacje korzystają z tunelu, obsługiwane listy dozwolonych i zabronionych aplikacji Androida już wyrażają politykę bez żadnego scrapowania. Zarezerwuj atrybucję na przepływ dla przypadków, które naprawdę jej potrzebują, i zaplanuj budżet na utrzymywaną implementację, która razem obsługuje kierunek, uprawnienia i nieznane stany.
Wybierz narzędzie według zadania, potem testuj krawędzie
Jeśli wymaganiem jest wybór tunelu, użyj listy platformy. Jeśli chodzi o atrybucję na żywo, użyj zapytania o właściciela i traktuj każde bezodpowiedziowe tuple jako nieznane. Jeśli to sumy historyczne, użyj stats API. Potem przetestuj trzy krawędzie, które łamią naiwne implementacje: pakiet w przeciwnym kierunku, przepływ zamykający się przed zapytaniem i wywołującego, który nie jest aktywnym VPN-em.
Nic z tego nie wymaga wyłączania SELinuxa, rootowania telefonu ani udostępniania tablic jądra globalnie. Ograniczenie jest sednem: platforma przeniosła atrybucję za kontrakt, aby odpowiedź była dostępna dla komponentu, który legalnie jej potrzebuje, i dla nikogo innego.