Attribution VPN sous Android : getConnectionOwnerUid face à /proc/net
Android 10 masque /proc/net aux applications. Un VPN reçoit toujours des paquets, mais l'attribution passe à getConnectionOwnerUid, avec ses propres règles de tuple et d'autorisation.
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.
| 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 |
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.