Sous Android 10 et versions ultérieures, une application qui ouvre /proc/net/tcp reçoit un refus d'accès au lieu d'une table de sockets. L'interface VPN s'en moque : les paquets arrivent toujours, car le tunnel est une décision de routage, pas une permission de fichier. Ce qui a changé, c'est la réponse à une question plus étroite : « quelle application possède cette connexion ? » Le remplacement est ConnectivityManager.getConnectionOwnerUid, la requête par connexion prise en charge, et le choix entre l'ancienne habitude et la nouvelle API dépend de laquelle des trois tâches vous avez réellement : recevoir des paquets, attribuer un flux ou totaliser l'usage.

Deux façons de répondre à la question du propriétaire

Avant l'API 29, un processus privilégié ou root pouvait lire les tables de sockets du noyau, faire correspondre le quadruplet et connaître l'UID propriétaire. Cela donnait un inventaire de tout l'appareil dans un seul fichier. Depuis l'API 29, la vue du système de fichiers est restreinte, et la voie prise en charge est une requête par connexion avec le protocole, l'adresse locale et l'adresse distante.

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

La comparaison n'est pas « ancien contre nouveau ». Ce sont trois tâches différentes qu'un seul fichier lisible satisfaisait autrefois et que trois interfaces différentes avec trois contrats différents satisfont désormais.

L'adresse locale est l'extrémité de l'application

La signature de la méthode prend un protocole, une adresse de socket locale et une adresse de socket distante. « Locale » désigne le côté de l'application dans la connexion, pas l'extrémité qui se trouve être la source du paquet au moment de l'inspection. Un paquet qui voyage du serveur distant vers l'application a sa source et sa destination inversées par rapport à cette définition.

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

Cette erreur de direction est la raison la plus fréquente pour laquelle une requête correcte renvoie une mauvaise réponse, ou aucune. Journalisez les deux extrémités avant l'appel et confirmez la paire par rapport à la connexion que l'application a réellement ouverte. La famille d'adresses compte aussi : une adresse mappée IPv4 et une adresse IPv6 native sont des tuples différents, tout comme les flux TCP et UDP sur les mêmes ports.

L'autorisation et les résultats inconnus font partie du contrat

La requête est réservée aux appelants autorisés, y compris l'application VPN active pour l'utilisateur concerné. Une application de surveillance sans rapport ne peut pas supposer pouvoir énumérer les connexions d'un autre profil, et un appel non autorisé peut lever une exception de sécurité plutôt que renvoyer une erreur aimable. Gérez explicitement les deux issues.

Un propriétaire absent n'est pas un nom de paquet. Le flux a pu se fermer entre le paquet et la requête, le tuple peut être incomplet, ou l'appelant peut simplement ne pas être autorisé à savoir. Mettre la première réponse réussie en cache et la réutiliser plus tard attribue une nouvelle connexion à une ancienne application, ce qui est pire que signaler une inconnue.

Quand l'attribution échoue, le trafic a toujours besoin d'une décision : le bloquer là où la politique l'exige, n'autoriser qu'un périmètre que vous comprenez, ou exposer la lacune. Ce repli relève de la conception, pas d'un commentaire.

La comptabilité est une autre question

NetworkStatsManager rapporte l'usage selon ses propres règles d'accès. Il peut dire combien un UID a transféré sur une période ; il ne peut pas dire le propriétaire d'un paquet vivant arbitraire. Les capacités réseau décrivent les propriétés d'un réseau, pas un annuaire de sockets. Mélanger les trois produit des tableaux de bord qui semblent cohérents et ne le sont pas.

Pour le cas simple de décider quelles applications utilisent le tunnel, les listes prises en charge d'applications autorisées et interdites d'Android expriment déjà la politique sans rien extraire. Réservez l'attribution par flux aux cas qui en ont vraiment besoin, et prévoyez une implémentation maintenue qui traite direction, permissions et états inconnus ensemble.

Choisissez l'outil selon la tâche, puis testez les bords

Si l'exigence est la sélection du tunnel, utilisez la liste de la plateforme. S'il s'agit d'attribution en direct, utilisez la requête de propriétaire et traitez chaque tuple sans réponse comme inconnu. S'il s'agit de totaux historiques, utilisez l'API de statistiques. Testez ensuite les trois bords qui cassent les implémentations naïves : un paquet en sens inverse, un flux qui se ferme avant votre requête, et un appelant qui n'est pas le VPN actif.

Rien de tout cela n'exige de désactiver SELinux, de rooter le téléphone ou de rendre les tables du noyau lisibles par tous. La contrainte est le point : la plateforme a déplacé l'attribution derrière un contrat pour que la réponse soit disponible pour le composant qui en a légitimement besoin, et pour personne d'autre.

Articles similaires