Android 切换用户后无法获取 MAC 地址分析解决

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,普通应用 ≥ 10000
  • 100000:用户偏移量(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的属性。

相关推荐
鸠摩智首席音效师2 小时前
如何在 MacOS 上安装 VirtualBox ?
macos
m0_613856292 小时前
mysql如何使用IF函数_mysql简单二元逻辑转换
jvm·数据库·python
爱喝热水的呀哈喽2 小时前
5步创建一个有不同numpy scipy版本的python环境
python·numpy·scipy
JJay.2 小时前
Android BLE 为什么连上了却收不到数据
android
歪楼小能手2 小时前
Android16在开机向导最后添加一个声明界面
android·java·平板
夏沫琅琊2 小时前
Android联系人导入导出
android·kotlin
pele2 小时前
如何在 Go 项目中安全、高效地共享 MySQL 数据库连接
jvm·数据库·python
承渊政道2 小时前
【动态规划算法】(斐波那契数列模型详解)
数据结构·c++·学习·算法·leetcode·macos·动态规划