getConnectionOwnerUid

发布于:2025-07-26 ⋅ 阅读:(15) ⋅ 点赞:(0)

在这里插入图片描述

在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聚合的流量统计信息。


网站公告

今日签到

点亮在社区的每一天
去签到