在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 inode
0: 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.java
private 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 文件描述符获取 UID
int 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聚合的流量统计信息。