En Android 10 y versiones posteriores, una aplicación que abre /proc/net/tcp recibe una denegación de acceso en lugar de una tabla de sockets. A la interfaz VPN no le importa: los paquetes siguen llegando, porque el túnel es una decisión de enrutamiento, no un permiso de archivo. Lo que cambió es la respuesta a una pregunta más estrecha: «¿qué aplicación posee esta conexión?» El reemplazo es ConnectivityManager.getConnectionOwnerUid, la consulta por conexión admitida, y elegir entre la vieja costumbre y la nueva API depende de cuál de tres tareas tenga realmente: recibir paquetes, atribuir un flujo o sumar consumo.

Dos formas de responder la pregunta del propietario

Antes de la API 29, un proceso privilegiado o root podía leer las tablas de sockets del kernel, emparejar la cuádrupla y conocer el UID propietario. Eso daba un inventario de todo el dispositivo en un solo archivo. Desde la API 29 la vista del sistema de archivos está restringida, y la ruta admitida es una consulta por conexión con el protocolo, la dirección local y la dirección remota.

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 comparación no es «viejo contra nuevo». Son tres tareas distintas que antes satisfacía un solo archivo legible y ahora satisfacen tres interfaces distintas con tres contratos distintos.

La dirección local es el extremo de la aplicación

La firma del método toma un protocolo, una dirección de socket local y una dirección de socket remota. «Local» significa el lado de la aplicación en la conexión, no el extremo que en el momento de la inspección resulta ser la fuente del paquete. Un paquete que viaja del servidor remoto de vuelta a la aplicación tiene el origen y el destino invertidos respecto de esa definición.

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

Este error de dirección es la razón más común por la que una consulta correcta devuelve una respuesta equivocada o ninguna. Registre ambos extremos antes de la llamada y confirme el par contra la conexión que la aplicación abrió realmente. La familia de direcciones también importa: una dirección mapeada por IPv4 y una dirección IPv6 nativa son tuplas distintas, y también lo son los flujos TCP y UDP en los mismos puertos.

La autorización y los resultados desconocidos forman parte del contrato

La consulta está restringida a llamadores autorizados, incluida la aplicación VPN activa para el usuario correspondiente. Una aplicación de monitoreo ajena no puede suponer que puede enumerar las conexiones de otro perfil, y una llamada no autorizada puede lanzar una excepción de seguridad en lugar de devolver un error amable. Gestione ambos resultados de forma explícita.

Un propietario ausente no es un nombre de paquete. El flujo pudo cerrarse entre el paquete y la consulta, la tupla puede estar incompleta, o puede que simplemente no se permita saberlo al llamador. Almacenar la primera respuesta correcta y reutilizarla después atribuye una conexión nueva a una aplicación vieja, lo que es peor que informar desconocido.

Cuando la atribución falla, el tráfico sigue necesitando una decisión: bloquearlo donde la política lo exija, permitir solo un ámbito que entienda, o mostrar el hueco. Ese respaldo pertenece al diseño, no a un comentario.

La contabilidad es otra pregunta

NetworkStatsManager informa del consumo según sus propias reglas de acceso. Puede decirle cuánto transfirió un UID durante un periodo; no puede decirle el propietario de un paquete vivo arbitrario. Las capacidades de red describen propiedades de una red, no un directorio de sockets. Mezclar las tres produce paneles que parecen coherentes y no lo son.

Para el caso simple de decidir qué aplicaciones usan el túnel, las listas admitidas de aplicaciones permitidas y denegadas de Android ya expresan la política sin extraer nada. Reserve la atribución por flujo para los casos que realmente la necesitan, y presupueste una implementación mantenida que trate dirección, permisos y estados desconocidos en conjunto.

Elija la herramienta según la tarea y luego pruebe los bordes

Si el requisito es la selección de túnel, use la lista de la plataforma. Si es atribución en vivo, use la consulta de propietario y trate cada tupla sin respuesta como desconocida. Si son totales históricos, use la API de estadísticas. Luego pruebe los tres bordes que rompen las implementaciones ingenuas: un paquete en sentido inverso, un flujo que se cierra antes de consultarlo y un llamador que no es la VPN activa.

Nada de esto requiere desactivar SELinux, hacer root al teléfono ni hacer que las tablas del kernel sean legibles globalmente. La restricción es el punto: la plataforma movió la atribución detrás de un contrato para que la respuesta esté disponible para el componente que legítimamente la necesita, y para nadie más.

Artículos relacionados