Atribuição de VPN no Android: getConnectionOwnerUid contra /proc/net
O Android 10 oculta /proc/net das aplicações. Uma VPN ainda recebe pacotes, mas a atribuição passa para getConnectionOwnerUid, com as suas próprias regras de tupla e permissões.
No Android 10 e posteriores, uma aplicação que abre /proc/net/tcp recebe uma negação de acesso em vez de uma tabela de sockets. A interface VPN não se importa: os pacotes continuam a chegar, porque o túnel é uma decisão de encaminhamento, não uma permissão de ficheiro. O que mudou é a resposta a uma pergunta mais estreita: «que aplicação é dona desta ligação?» O substituto é ConnectivityManager.getConnectionOwnerUid, a consulta por ligação suportada, e a escolha entre o velho hábito e a nova API depende de qual das três tarefas realmente tem: receber pacotes, atribuir um fluxo ou somar consumo.
Duas formas de responder à pergunta do proprietário
Antes da API 29, um processo privilegiado ou root podia ler as tabelas de sockets do kernel, emparelhar o quádruplo e saber o UID proprietário. Isso dava um inventário de todo o dispositivo num único ficheiro. Desde a API 29 a vista do sistema de ficheiros está restrita, e o caminho suportado é uma consulta por ligação com o protocolo, o endereço local e o endereço remoto.
| 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 |
A comparação não é «antigo contra novo». São três tarefas diferentes que antes eram satisfeitas por um único ficheiro legível e agora são satisfeitas por três interfaces diferentes com três contratos diferentes.
O endereço local é a extremidade da aplicação
A assinatura do método recebe um protocolo, um endereço de socket local e um endereço de socket remoto. «Local» significa o lado da aplicação na ligação, não a extremidade que no momento da inspeção é a origem do pacote. Um pacote que viaja do servidor remoto de volta para a aplicação tem origem e destino invertidos em relação a essa definição.
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
int uid = cm.getConnectionOwnerUid(
OsConstants.IPPROTO_TCP, appLocalAddress, appRemoteAddress);Este erro de direção é a razão mais comum para uma consulta correta devolver a resposta errada, ou nenhuma. Registe ambas as extremidades antes da chamada e confirme o par contra a ligação que a aplicação abriu realmente. A família de endereços também importa: um endereço mapeado por IPv4 e um endereço IPv6 nativo são tuplas diferentes, tal como os fluxos TCP e UDP nas mesmas portas.
A autorização e os resultados desconhecidos fazem parte do contrato
A consulta está restrita a chamadores autorizados, incluindo a aplicação VPN ativa para o utilizador em causa. Uma aplicação de monitorização alheia não pode assumir que consegue enumerar as ligações de outro perfil, e uma chamada não autorizada pode lançar uma exceção de segurança em vez de devolver um erro amigável. Trate ambos os resultados de forma explícita.
Um proprietário ausente não é um nome de pacote. O fluxo pode ter fechado entre o pacote e a consulta, a tupla pode estar incompleta, ou pode simplesmente não ser permitido ao chamador saber. Guardar a primeira resposta bem-sucedida e reutilizá-la mais tarde atribui uma ligação nova a uma aplicação antiga, o que é pior do que reportar desconhecido.
Quando a atribuição falha, o tráfego continua a precisar de uma decisão: bloqueá-lo onde a política o exige, permitir apenas um âmbito que compreenda, ou expor a lacuna. Esse recurso pertence ao desenho, não a um comentário.
A contabilidade é outra pergunta
NetworkStatsManager reporta o consumo segundo as suas próprias regras de acesso. Pode dizer quanto um UID transferiu num período; não pode dizer o proprietário de um pacote vivo arbitrário. As capacidades de rede descrevem propriedades de uma rede, não um diretório de sockets. Misturar as três produz painéis que parecem coerentes e não são.
Para o caso simples de decidir que aplicações usam o túnel, as listas suportadas de aplicações permitidas e proibidas do Android já expressam a política sem extrair nada. Reserve a atribuição por fluxo para os casos que realmente precisam dela, e orçamente uma implementação mantida que trate direção, permissões e estados desconhecidos em conjunto.
Escolha a ferramenta pela tarefa e depois teste as arestas
Se o requisito é a seleção de túnel, use a lista da plataforma. Se é atribuição em tempo real, use a consulta de proprietário e trate cada tupla sem resposta como desconhecida. Se são totais históricos, use a API de estatísticas. Depois teste as três arestas que quebram implementações ingénuas: um pacote no sentido inverso, um fluxo que fecha antes da consulta e um chamador que não é a VPN ativa.
Nada disto exige desativar o SELinux, fazer root ao telemóvel ou tornar as tabelas do kernel legíveis globalmente. A restrição é o ponto: a plataforma moveu a atribuição para trás de um contrato para que a resposta esteja disponível para o componente que legitimamente precisa dela, e para mais ninguém.