当前位置: 代码网 > it编程>操作系统>苹果Mac > Android切换用户后无法获取MAC地址的解决方法

Android切换用户后无法获取MAC地址的解决方法

2026年04月27日 苹果Mac 我要评论
一、前言最近在做 android 多用户功能适配时,发现一个问题:在主用户下可以正常获取有线网(ethernet)的 mac 地址,但切换到子用户后,通过 `networkinterface.geth

一、前言

最近在做 android 多用户功能适配时,发现一个问题:

在主用户下可以正常获取有线网(ethernet)的 mac 地址,但切换到子用户后,通过 `networkinterface.gethardwareaddress()` 获取到的 mac 地址为 null。
获取mac地址,需要系统应用或者系统权限应用;普通应用是获取不到的;
目前问题是系统权限应用,在子用户下也是无法获取到有线网节点eth0的mac地址。

这个问题影响了子用户下的网络信息展示、设备标识、某些应用激活等功能。

经过分析,问题出在 bionic 库的 ifaddrs.cpp 中,对多用户场景的 uid 判断代码。

本文记录完整的分析过程和解决方案。

android13 之后好像就有这个问题,本文的代码具体代码展示是android16的。

二、问题现象

在 android 设备上创建子用户(userid=10)后,子用户中的系统应用调用以下代码获取 mac 地址:

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 字段:

// 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() 函数:

// 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 值:

场景useridappidgetuid() 返回值
主用户 system_server010001000
主用户 shell020002000
主用户普通应用01006810068
子用户 system1010001001000
子用户 shell1020001002000
子用户普通应用10100681010068

4、问题根因定位

原代码的判断条件:

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 后再比较:

修改前:

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);
  }
  // ...
}

修改后:

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
主用户 system1000✅ 发送 rtm_getlink✅ 发送
主用户普通应用10068❌ 不发送❌ 不发送
子用户 system1001000不发送(问题)✅ 发送(appid=1000)
子用户普通应用1010068❌ 不发送❌ 不发送(appid=10068)

修改后,子用户的系统应用可以正常获取 mac 地址。

普通应用可以吗?测试了一下还是不行!

就算强制进入获取 getlink_success 的逻辑,普通应用还是会返回0;

估计还要适配系统其他权限问题,比较麻烦所以这个解决方案对普通应用不行。

并且这种修改对 edla 认证可能会有影响,不建议使用。

3、应用层替代方案

如果不方便修改 bionic 层,也可以在应用层(系统应用)通过读取 sysfs 文件来获取 mac 地址:

/**
 * 通过 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

# 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

这个方案也是只能适配系统应用,并且要适配权限,比较麻烦,不建议修改。

4、主线程的服务/应用获取并记录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设置权限的,只有读取权限。

5、让普通用户也可以设置和获取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地址。

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的属性。

以上就是android切换用户后无法获取mac地址的解决方法的详细内容,更多关于android无法获取mac地址的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com