Unter Android 10 und neuer erhält eine App, die /proc/net/tcp öffnet, eine Zugriffsverweigerung statt einer Socket-Tabelle. Das VPN-Interface stört das nicht: Pakete kommen weiterhin an, denn der Tunnel ist eine Routing-Entscheidung, keine Dateiberechtigung. Geändert hat sich die Antwort auf eine engere Frage: „Welche Anwendung besitzt diese Verbindung?“ Der Ersatz ist ConnectivityManager.getConnectionOwnerUid, die unterstützte Abfrage pro Verbindung, und die Wahl zwischen der alten Gewohnheit und der neuen API hängt davon ab, welche der drei Aufgaben Sie tatsächlich haben: Pakete empfangen, einen Fluss attribuieren oder Nutzung summieren.

Zwei Wege, die Eigentümerfrage zu beantworten

Vor API 29 konnte ein privilegierter Prozess oder Root die Socket-Tabellen des Kernels lesen, das Vier-Tupel abgleichen und die besitzende UID ermitteln. Das ergab ein geräteweites Inventar in einer Datei. Ab API 29 ist die Dateisystem-Sicht eingeschränkt, und der unterstützte Weg ist eine Abfrage pro Verbindung mit Protokoll, lokaler Adresse und Remote-Adresse.

QuestionLegacy socket tablesgetConnectionOwnerUid
Who owns this flow?Scan and match the tupleAsk once per tuple
Device-wide inventory?Yes, when readableNo, one flow at a time
Historical usage?NoNo; that is NetworkStatsManager
Works without root?No on modern releasesYes, for authorized callers

Der Vergleich lautet nicht „alt gegen neu“. Es sind drei verschiedene Aufgaben, die früher von einer lesbaren Datei erfüllt wurden und heute von drei verschiedenen Schnittstellen mit drei verschiedenen Verträgen erfüllt werden.

Die lokale Adresse ist der Endpunkt der App

Die Methodensignatur nimmt ein Protokoll, eine lokale Socket-Adresse und eine Remote-Socket-Adresse. „Lokal“ meint die Seite der Anwendung in der Verbindung, nicht den Endpunkt, der im Moment der Prüfung zufällig die Paketquelle ist. Ein Paket, das vom entfernten Server zurück zur App läuft, hat Quelle und Ziel gegenüber dieser Definition vertauscht.

ConnectivityManager cm = getSystemService(ConnectivityManager.class);
int uid = cm.getConnectionOwnerUid(
    OsConstants.IPPROTO_TCP, appLocalAddress, appRemoteAddress);

Dieser Richtungsfehler ist der häufigste Grund, warum eine funktionierende Abfrage die falsche Antwort oder gar keine liefert. Protokollieren Sie beide Endpunkte vor dem Aufruf und prüfen Sie das Paar gegen die Verbindung, die die App tatsächlich geöffnet hat. Auch die Adressfamilie zählt: Eine IPv4-gemappte Adresse und eine native IPv6-Adresse sind verschiedene Tupel, ebenso TCP- und UDP-Flüsse auf denselben Ports.

Autorisierung und unbekannte Ergebnisse sind Teil des Vertrags

Die Abfrage ist auf autorisierte Aufrufer beschränkt, einschließlich der aktiven VPN-Anwendung für den betreffenden Benutzer. Eine unbeteiligte Überwachungs-App kann nicht annehmen, die Verbindungen eines anderen Profils aufzählen zu können, und ein nicht autorisierter Aufruf kann eine Sicherheitsausnahme auslösen, statt einen freundlichen Fehler zurückzugeben. Behandeln Sie beide Ausgänge ausdrücklich.

Ein fehlender Eigentümer ist kein Paketname. Der Fluss kann zwischen Paket und Abfrage geschlossen worden sein, das Tupel unvollständig oder dem Aufrufer schlicht nicht erlaubt sein, es zu wissen. Die erste erfolgreiche Antwort zu cachen und später wiederzuverwenden, ordnet eine neue Verbindung einer alten App zu, was schlechter ist, als unbekannt zu melden.

Wenn die Attribution scheitert, braucht der Verkehr trotzdem eine Entscheidung: blockieren, wo die Richtlinie es verlangt, nur einen verstandenen Bereich zulassen oder die Lücke sichtbar machen. Dieser Fallback gehört ins Design, nicht in einen Kommentar.

Abrechnung ist eine andere Frage

NetworkStatsManager meldet Nutzung nach eigenen Zugriffsregeln. Es kann sagen, wie viel eine UID über einen Zeitraum übertragen hat; es kann nicht den Eigentümer eines beliebigen lebenden Pakets sagen. Netzwerkfähigkeiten beschreiben Eigenschaften eines Netzwerks, kein Verzeichnis von Sockets. Die drei zu vermischen erzeugt Dashboards, die konsistent aussehen und es nicht sind.

Für den einfachen Fall zu entscheiden, welche Apps den Tunnel nutzen, drücken Androids unterstützte Listen erlaubter und verbotener Anwendungen die Richtlinie bereits aus, ohne irgendetwas zu scrapen. Reservieren Sie die Attribution pro Fluss für die Fälle, die sie wirklich brauchen, und kalkulieren Sie eine gepflegte Implementierung ein, die Richtung, Berechtigungen und unbekannte Zustände zusammen behandelt.

Wählen Sie das Werkzeug nach Aufgabe, dann testen Sie die Kanten

Wenn die Anforderung Tunnelauswahl ist, nutzen Sie die Plattformliste. Wenn es Live-Attribution ist, nutzen Sie die Eigentümerabfrage und behandeln jedes unbeantwortbare Tupel als unbekannt. Wenn es historische Summen sind, nutzen Sie die Stats-API. Testen Sie dann die drei Kanten, die naive Implementierungen brechen: ein Paket in Gegenrichtung, ein Fluss, der vor der Abfrage schließt, und ein Aufrufer, der nicht das aktive VPN ist.

Nichts davon erfordert, SELinux zu deaktivieren, das Telefon zu rooten oder Kerneltabellen global lesbar zu machen. Die Einschränkung ist der Punkt: Die Plattform hat die Attribution hinter einen Vertrag verschoben, damit die Antwort der Komponente zur Verfügung steht, die sie legitim braucht, und niemandem sonst.

Ähnliche Artikel