Android 切换用户后无法获取 MAC 地址分析解决
文章目录
### 文章目录
- [Android 切换用户后无法获取 MAC 地址分析解决](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [文章目录](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [@[toc]](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [一、前言](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [二、问题现象](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [三、原因分析](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [1、NetworkInterface.getHardwareAddress() 调用链路](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [2、getifaddrs() 中的 UID 判断逻辑](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [3、Android 多用户 UID 计算规则](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [4、问题根因定位](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [四、解决方案](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [1、修改NetworkInterface.getHardwareAddress()的返回信息 ?](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [2、修改 bionic 层 ifaddrs.cpp](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [修改前后效果对比](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [2、应用层替代方案](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [3、主线程的服务/应用获取并记录mac地址](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [4、让普通用户也可以设置和获取Settings.Global 属性](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [五、其他](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [1、小结](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [2、获取ip和mac地址几种方式](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
- [3、普通应用反射获取prop的封装方法](#文章目录 Android 切换用户后无法获取 MAC 地址分析解决 文章目录 @[toc] 一、前言 二、问题现象 三、原因分析 1、NetworkInterface.getHardwareAddress() 调用链路 2、getifaddrs() 中的 UID 判断逻辑 3、Android 多用户 UID 计算规则 4、问题根因定位 四、解决方案 1、修改NetworkInterface.getHardwareAddress()的返回信息 ? 2、修改 bionic 层 ifaddrs.cpp 修改前后效果对比 2、应用层替代方案 3、主线程的服务/应用获取并记录mac地址 4、让普通用户也可以设置和获取Settings.Global 属性 五、其他 1、小结 2、获取ip和mac地址几种方式 3、普通应用反射获取prop的封装方法)
一、前言
最近在做 Android 多用户功能适配时,发现一个问题:
在主用户下可以正常获取有线网(Ethernet)的 MAC 地址,但切换到子用户后,通过 `NetworkInterface.getHardwareAddress()` 获取到的 MAC 地址为 null。
获取mac地址,需要系统应用或者系统权限应用;普通应用是获取不到的;
目前问题是系统权限应用,在子用户下也是无法获取到有线网节点eth0的mac地址。
这个问题影响了子用户下的网络信息展示、设备标识、某些应用激活等功能。
经过分析,问题出在 bionic 库的 ifaddrs.cpp 中,对多用户场景的 UID 判断代码。
本文记录完整的分析过程和解决方案。
Android13 之后好像就有这个问题,本文的代码具体代码展示是Android16的。
二、问题现象
在 Android 设备上创建子用户(userId=10)后,子用户中的系统应用调用以下代码获取 MAC 地址:
java
NetworkInterface ni = NetworkInterface.getByName("eth0");
byte[] mac = ni.getHardwareAddress(); // 子用户下返回 null
| 场景 | 结果 |
|---|---|
| 主用户(userId=0)系统应用 | ✅ 正常返回 MAC 地址 |
| 主用户(userId=0)普通应用 | ❌ 返回 null(正常,安全限制) |
| 子用户(userId=10)系统应用 | ❌ 返回 null(异常,本文要解决的问题) |
| 子用户(userId=10)普通应用 | ❌ 返回 null(正常,安全限制) |
三、原因分析
1、NetworkInterface.getHardwareAddress() 调用链路
从应用层到内核层的完整调用链路如下:
应用层: NetworkInterface.getHardwareAddress()
↓
Java 层: libcore/ojluni/src/main/java/java/net/NetworkInterface.java
↓ 返回 ni.hardwareAddr 字段
JNI 层: libcore/luni/src/main/native/libcore_io_Linux.cpp
↓ 调用 getifaddrs()
Bionic 层: bionic/libc/bionic/ifaddrs.cpp ← 【问题所在】
↓ 通过 netlink 发送 RTM_GETLINK 请求
内核层: netlink socket → 返回 AF_PACKET 类型地址(包含 MAC)
getHardwareAddress() 的 Java 层代码本身没有做 UID 权限判断,它只是返回 hardwareAddr 字段:
java
// libcore/ojluni/src/main/java/java/net/NetworkInterface.java
public byte[] getHardwareAddress() throws SocketException {
NetworkInterface ni = getByName(name);
if (ni == null) {
throw new SocketException("NetworkInterface doesn't exist anymore");
}
if (ni.hardwareAddr == null && !"lo".equals(name)
&& !Compatibility.isChangeEnabled(RETURN_NULL_HARDWARE_ADDRESS)) {
return DEFAULT_MAC_ADDRESS.clone(); // 02:00:00:00:00:00
}
return ni.hardwareAddr; // 关键:这个值来自 native 层
}
hardwareAddr 的值是在 native 层通过 getifaddrs() 获取并填充的。如果 getifaddrs() 没有返回 AF_PACKET 类型的地址信息,hardwareAddr 就是 null。
2、getifaddrs() 中的 UID 判断逻辑
问题的根源在 bionic/libc/bionic/ifaddrs.cpp 中的 getifaddrs() 函数:
cpp
// bionic/libc/bionic/ifaddrs.cpp
int getifaddrs(ifaddrs** out) {
*out = nullptr;
NetlinkConnection nc;
// 关键判断:只有 uid < 10000 的进程才发送 RTM_GETLINK 请求
bool getlink_success = false;
if (getuid() < FIRST_APPLICATION_UID) { // FIRST_APPLICATION_UID = 10000
getlink_success =
nc.SendRequest(RTM_GETLINK) && nc.ReadResponses(__getifaddrs_callback, out);
}
bool getaddr_success =
nc.SendRequest(RTM_GETADDR) && nc.ReadResponses(__getifaddrs_callback, out);
// ...
}
这里的逻辑是:
RTM_GETLINK:获取网络接口的链路层信息(包含 MAC 地址),只对 uid < 10000 的进程发送RTM_GETADDR:获取网络接口的 IP 地址信息,所有进程都可以发送
注释也说明了原因:SELinux policy only allows RTM_GETLINK messages to be sent by system apps。
3、Android 多用户 UID 计算规则
Android 多用户下,UID 的计算公式为:
uid = userId * 100000 + appId
其中:
userId:用户 ID,主用户为 0,子用户从 10 开始appId:应用 ID,系统进程 < 10000,普通应用 ≥ 10000100000:用户偏移量(AID_USER_OFFSET)
各场景下的 UID 值:
| 场景 | userId | appId | getuid() 返回值 |
|---|---|---|---|
| 主用户 system_server | 0 | 1000 | 1000 |
| 主用户 shell | 0 | 2000 | 2000 |
| 主用户普通应用 | 0 | 10068 | 10068 |
| 子用户 system | 10 | 1000 | 1001000 |
| 子用户 shell | 10 | 2000 | 1002000 |
| 子用户普通应用 | 10 | 10068 | 1010068 |
4、问题根因定位
原代码的判断条件:
cpp
if (getuid() < FIRST_APPLICATION_UID) // 即 getuid() < 10000
- 主用户 system(uid=1000):1000 < 10000 ✅ → 发送 RTM_GETLINK → 获取到 MAC
- 子用户 system(uid=1001000):1001000 < 10000 ❌ → 不发送 RTM_GETLINK → MAC 为 null
问题根因:ifaddrs.cpp 使用完整的 uid 做判断,没有考虑多用户场景。子用户的系统进程 uid 远大于 10000,被错误地当作普通应用处理,导致 RTM_GETLINK 请求不会发送,MAC 地址无法获取。
四、解决方案
1、修改NetworkInterface.getHardwareAddress()的返回信息 ?
这个是肯定不行的。
因为NetworkInterface 的代码位置在 libcore/ojluni/src/main/java/java/net/NetworkInterface.java。
这个是Java的类包,无法导入Android的类,获取不到Android的信息。
并且这个类库不是随系统编译的,试过代码中加入Java打印,编译验证是没有的显示的,
估计要用特殊指令单独编译这块代码,才会更新系统相关依赖包。
2、修改 bionic 层 ifaddrs.cpp
文件路径:bionic/libc/bionic/ifaddrs.cpp
将 getuid() 的判断改为提取 appId 后再比较:
修改前:
cpp
int getifaddrs(ifaddrs** out) {
*out = nullptr;
NetlinkConnection nc;
bool getlink_success = false;
if (getuid() < FIRST_APPLICATION_UID) {
getlink_success =
nc.SendRequest(RTM_GETLINK) && nc.ReadResponses(__getifaddrs_callback, out);
}
// ...
}
修改后:
cpp
int getifaddrs(ifaddrs** out) {
*out = nullptr;
NetlinkConnection nc;
// 修改:使用 appId 判断,支持多用户场景
// Android 多用户下 uid = userId * 100000 + appId
// 子用户的系统应用 appId 仍然 < FIRST_APPLICATION_UID
bool getlink_success = false;
uid_t appId = getuid() % 100000;
if (appId < FIRST_APPLICATION_UID) {
getlink_success =
nc.SendRequest(RTM_GETLINK) && nc.ReadResponses(__getifaddrs_callback, out);
}
// ...
}
核心改动就一行:把 getuid() 换成 getuid() % 100000。
100000 是 Android 中用户偏移量(AID_USER_OFFSET)的固定值,从未改变过。通过取模运算提取出 appId,就能正确识别所有用户下的系统进程。
修改前后效果对比
| 场景 | uid | 原逻辑 getuid() < 10000 |
修改后 getuid() % 100000 < 10000 |
|---|---|---|---|
| 主用户 system | 1000 | ✅ 发送 RTM_GETLINK | ✅ 发送 |
| 主用户普通应用 | 10068 | ❌ 不发送 | ❌ 不发送 |
| 子用户 system | 1001000 | ❌ 不发送(问题) | ✅ 发送(appId=1000) |
| 子用户普通应用 | 1010068 | ❌ 不发送 | ❌ 不发送(appId=10068) |
修改后,子用户的系统应用可以正常获取 MAC 地址。
普通应用可以吗?测试了一下还是不行!
就算强制进入获取 getlink_success 的逻辑,普通应用还是会返回0;
估计还要适配系统其他权限问题,比较麻烦所以这个解决方案对普通应用不行。
并且这种修改对 EDLA 认证可能会有影响,不建议使用。
2、应用层替代方案
如果不方便修改 bionic 层,也可以在应用层(系统应用)通过读取 sysfs 文件来获取 MAC 地址:
java
/**
* 通过 sysfs 获取有线网 MAC 地址
* 不依赖 NetworkInterface.getHardwareAddress(),不受 bionic 层 UID 限制
*/
public static String getEthernetMac() {
try {
return new String(Files.readAllBytes(
Paths.get("/sys/class/net/eth0/address"))).trim();
} catch (IOException e) {
Log.e(TAG, "Failed to read MAC address from sysfs", e);
return null;
}
}
但这种方式需要 SELinux 策略允许应用读取 sysfs_net:
te
# device/<vendor>/<device>/sepolicy/private/your_app.te
allow your_app_domain sysfs_net:file { read open getattr };
allow your_app_domain sysfs_net:dir { search };
UserDebug版本确实可以通过cat sys/class/net/eth0/address 获取有线网mac地址
但是配置策略比较麻烦,有需要的可以自行测试。
wifi的mac地址同理:sys/class/net/wlan0/address
这个方案也是只能适配系统应用,并且要适配权限,比较麻烦,不建议修改。
3、主线程的服务/应用获取并记录mac地址
可以在主用户进程中获取 MAC 地址写入个系统属性,子用户直接读属性:
String mac= getMacFromNetworkInterface();//主用户可以拿到
SystemProperties.set("persist.debug.eth.mac",mac);
// 子用户应用中,非系统应用需要反射获取
String mac=SystemProperties.get("persist.debug.etho.mac","");
可以在系统wifi服务或者自定义的系统应用服务启动时获取mac,切换用户过程,这些服务是一直在的。
这方案修改代码最少,又不影响系统其他功能。
如果不行用prop属性,是否可以用Settings属性?
一般的Settings.System、Secure都时候会重置的,Global属性不会重置,这个获取和设置更加简单一点。
//系统服务
Settings.Global.putString(getContentResolver(), "mac_address", macStringXXX);
//普通应用,直接调用,不用反射
String deviceMac = Settings.Global.getString(getContentResolver(),"mac_address");
这个是目前最简单的实现方式,Settings.Global 保存和获取mac地址信息;
普通应用是没有Settings设置权限的,只有读取权限。
4、让普通用户也可以设置和获取Settings.Global 属性
这个需要修改framework的代码,也是不太建议的。
系统修改下面两个地方其中一个:
DefaultPermissionGrantPolicy.java → 给指定包名自动授权
grantPermissionsToPackage
PermissionManagerService.java → 全局放行权限(所有应用都能用)
private boolean checkPermission(String perm, int pid, int uid, boolean debug)
普通应用定义权限:
android.permission.WRITE_SECURE_SETTINGS
android.permission.WRITE_GLOBAL_SETTINGS
之前普通应用就可以通过Settings.Global.putString 和 Settings.Global.getString 设置获取属性;
但是这个是破坏Android安全性的,EDLA认证是会有报错的。
五、其他
1、小结
Android 切换用户后无法获取 MAC 地址的根本原因是 bionic/libc/bionic/ifaddrs.cpp 中的 getifaddrs() 函数使用完整的 uid 做权限判断,没有考虑多用户场景。
子用户的系统进程 uid(如 1001000)远大于 FIRST_APPLICATION_UID(10000),被错误地当作普通应用,导致 RTM_GETLINK 请求不会发送,NetworkInterface.getHardwareAddress() 返回 null。
解决方案是将 getuid() 改为 getuid() % 100000,提取 appId 后再做判断,这样所有用户下的系统进程都能正确获取 MAC 地址,同时不影响普通应用的安全限制。
修改方式和修改涉及的文件:
-
bionic 层 :
bionic/libc/bionic/ifaddrs.cpp(核心修改,改一行代码) -
SELinux 策略 :如有需要,确保子用户的系统应用有
netlink_route_socket权限 -
应用层替代 :可通过读取
/sys/class/net/eth0/address绕过,需配置 SELinux 策略 -
系统服务:系统服务启动时获取mac地址保存到prop属性或者Settings.Global ,子用户可以读取。
目前验证测试,通过系统服务设置Settings.Global 的mac属性,普通用户获取 Settings.Global 是最简单的。
2、获取ip和mac地址几种方式
1、ifconfig
2、获取wifi 的ip
可以通过 WifiManager 获取当前连接的wifi信息,获取到ip地址;
3、获取有线网、wifi的ip、mac
通过获取 ConnectivityManager获取连接的网络 Network-->LinkProperties获取到ip地址。
4、获取有线网、wifi、热点、p2p的ip、mac
通过获取所有节点信息:NetworkInterface.getNetworkInterfaces() 获取对应的ip地址和MAC地址。
具体代码参考:https://blog.csdn.net/wenzhi20102321/article/details/141673195
3、普通应用反射获取prop的封装方法
封装类和方法,可以直接使用:
import android.text.TextUtils;
import android.util.Log;
import java.lang.reflect.Method;
public class SystemPropertiesUtil {
private static final String TAG = "SystemPropertiesUtil";
private static Method sGetMethod;
private static Method sSetMethod;
static {
try {
Class<?> clazz = Class.forName("android.os.SystemProperties");
sGetMethod = clazz.getMethod("get", String.class, String.class);
sSetMethod = clazz.getMethod("set", String.class, String.class);
} catch (Exception e) {
Log.e(TAG, "Failed to init SystemProperties methods", e);
}
}
/**
* 获取系统属性值
*
* @param key 属性名,如 "ro.build.display.id"
* @param defaultValue 默认值,属性不存在或无权限时返回
* @return 属性值
*/
public static String get(String key, String defaultValue) {
try {
if (sGetMethod != null) {
String value = (String) sGetMethod.invoke(null, key, defaultValue);
return value;
}
} catch (Exception e) {
Log.e(TAG, "Failed to get property: " + key, e);
}
return defaultValue;
}
/**
* 获取系统属性值,默认返回空串
*/
public static String get(String key) {
return get(key, "");
}
/**
* 获取 boolean 类型属性
*/
public static boolean getBoolean(String key, boolean defaultValue) {
String value = get(key, "");
if (TextUtils.isEmpty(value)) return defaultValue;
return "true".equalsIgnoreCase(value) || "1".equals(value);
}
/**
* 获取 int 类型属性
*/
public static int getInt(String key, int defaultValue) {
String value = get(key, "");
try {
return TextUtils.isEmpty(value) ? defaultValue : Integer.parseInt(value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* 设置系统属性(普通应用通常没有权限,仅系统应用可用)
*/
public static void set(String key, String value) {
try {
if (sSetMethod != null) {
sSetMethod.invoke(null, key, value);
}
} catch (Exception e) {
Log.e(TAG, "Failed to set property: " + key, e);
}
}
}
如果系统应用通过prop设置mac地址属性,普通用户就用上面这个封装方法获取prop的属性。