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