getConnectionOwnerUid
在Android系统中,为了进行网络权限控制、流量统计等,需要将网络连接(如Socket)与发起该连接的应用UID关联起来。这种关联通常在内核中建立,并在用户空间通过一些接口进行查询。
1. 内核中的实现基础
Linux内核中,每个Socket都有一个关联的struct sock结构。在该结构中,有一个字段用于存储用户ID(UID):
struct sock { // ... kuid_t sk_uid; // 存储创建该socket的进程的UID // ...};
当应用创建一个Socket时,内核会将该进程的UID(即进程的有效UID)记录在sock->sk_uid中。
该字段在 Socket 创建时由内核自动填充(通过 current_uid()
获取当前进程 UID)。
2. 用户空间查询连接UID的方法
在用户空间,Android系统提供了几种方式来获取网络连接的UID:
(1) 使用/proc/net文件系统
Linux内核通过/proc/net下的文件(如/proc/net/tcp、/proc/net/udp等)暴露TCP和UDP连接的信息。这些文件中包含的每一行代表一个连接,其中有一列是UID(在Android中,该列是第7列,索引从0开始计数)。例如:
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode0: 0100007F:1770 00000000:0000 0A 00000000:00000000 00:00000000 00000000 10086 0 123456
这里,UID为10086。
在Android系统中,系统服务(如NetworkStatsService)会解析这些文件以获取每个连接的UID,从而进行流量统计。
(2) 使用Netlink Socket
更高效的方式是使用Netlink Socket(具体为NETLINK_INET_DIAG)来查询Socket信息。Android系统使用NetlinkSocket类(位于frameworks/base/core/jni/android_net_Netlink.cpp)来发送查询请求并解析响应。
在NetlinkSocket的请求中,可以指定查询条件(如协议、状态等),内核会返回匹配的Socket信息,其中就包括UID。
例如,在NetworkStatsService中,通过netlink方式收集Socket信息:
// 在Java层,通过调用Native方法// frameworks/base/services/core/java/com/android/server/net/NetworkStatsService.javaprivate void readNetworkStatsDev() { // ... mNetworkDevStats = new NetworkStats(SystemClock.elapsedRealtime(), 6); mNetworkDevStats = NetworkStatsService.nativeReadNetworkDevStats(); // ...}
在Native层(frameworks/base/services/core/jni/com_android_server_net_NetworkStatsService.cpp)中,最终会调用netlink相关函数来获取信息。
或通过 Netlink Socket 发送 inet_diag_req
请求,内核返回 inet_diag_msg
结构体,其中包含 idiag_uid
字段:
struct inet_diag_msg { __u8 idiag_family; __u8 idiag_state; // ... __u32 idiag_uid; // 连接的 UID __u32 idiag_inode; // Socket 的 inode};
这是 NetworkStatsService
等系统服务内部使用的机制
(3) 系统API:TrafficStats.getUidRxBytes(int uid)等
Android提供了TrafficStats类,其中包含一些静态方法,如:
getUidRxBytes(int uid)
getUidTxBytes(int uid)
这些方法允许应用获取指定UID的流量统计。其内部实现也是通过读取/proc/net文件或使用netlink,然后按UID聚合统计信息。
- 系统 API(隐藏 API)
Android 框架中部分类提供内部方法,如:
// 示例:通过 Socket 文件描述符获取 UIDint uid = android.os.Process.getUidForSocket(int socketFd);
实际实现通过 JNI 调用到 libcore
中的 native 方法
(4) 系统服务中的连接跟踪
在Android系统服务中,如ConnectivityService、NetworkStatsService等,会定期扫描网络连接,并记录每个连接的UID。这些信息可以用于防火墙规则(如根据UID阻止网络访问)或网络统计。
3. 具体实现:getConnectionOwnerUid的类似功能
如果我们要实现一个getConnectionOwnerUid函数,其功能是给定一个本地地址和端口(或远程地址和端口),返回该连接的UID,那么可以通过以下步骤:
查询/proc/net文件:遍历/proc/net/tcp和/proc/net/udp等文件,查找匹配本地地址和端口的行,然后提取UID列。
使用netlink查询:构造一个inet_diag_req请求,设置查询条件(如本地地址和端口),然后发送请求并解析响应,从响应中获取UID。
4. 示例:通过/proc/net/tcp获取UID
以下是一个简化的示例,展示如何从/proc/net/tcp中获取指定本地地址和端口的连接的UID:
public static int getConnectionOwnerUid(String protocol, String localAddr, int localPort) { String procFile = \"/proc/net/\" + protocol; // 如 \"tcp\" 或 \"udp\" try (BufferedReader reader = new BufferedReader(new FileReader(procFile))) { String line; // 跳过标题行 reader.readLine(); while ((line = reader.readLine()) != null) { // 按空格分割,注意可能有多个连续空格 String[] tokens = line.trim().split(\"\\\\s+\"); if (tokens.length < 10) continue; // 本地地址和端口在第二个字段(索引1) String local = tokens[1]; // 解析本地地址和端口 String[] localParts = local.split(\":\"); if (localParts.length != 2) continue; // 将16进制字符串形式的地址和端口转换为可读格式 String addr = parseHexAddress(localParts[0]); int port = Integer.parseInt(localParts[1], 16); // 比较是否匹配 if (addr.equals(localAddr) && port == localPort) { // UID在第8个字段(索引7) return Integer.parseInt(tokens[7]); } } } catch (IOException e) { e.printStackTrace(); } return -1; // 未找到}private static String parseHexAddress(String hex) { // 处理IPv4地址:4字节,小端序 long addr = Long.parseLong(hex, 16); return String.format(\"%d.%d.%d.%d\", (addr & 0xff), ((addr >> 8) & 0xff), ((addr >> 16) & 0xff), ((addr >> 24) & 0xff));}
注意:上述代码仅为示例,实际应用中需要考虑IPv6、性能优化(避免频繁读取proc文件)以及权限问题(需要root权限才能读取/proc/net文件)。
5. 权限要求
读取/proc/net下的文件需要root权限,因为普通应用无法访问这些文件。
使用netlink也需要root权限,因为创建NETLINK_INET_DIAG类型的socket需要CAP_NET_ADMIN能力。
6. 在Android系统中的应用
在Android系统内部,如NetworkStatsService、NetworkManagementService等系统服务中,会使用这些机制来跟踪每个连接的UID,以便进行流量统计和网络策略管理。
Android 版本差异
- Android 7.0+ 强化了 UID 隔离,
/proc/net
文件权限更严格。 - Android 10+ 引入
CONNTRACK_UID
模块替代部分旧机制。
总结
Android系统通过内核记录每个Socket的创建者UID。
用户空间可以通过读取/proc/net下的文件或使用netlink机制来查询活跃连接的UID。
这些查询通常需要root权限,因此主要用于系统服务内部。
普通应用无法直接使用这些方法,但可以通过系统API(如TrafficStats)获取按UID聚合的流量统计信息。