最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android切換用戶后無法獲取MAC地址的解決方法

 更新時間:2026年04月27日 09:21:12   作者:崢嶸life  
本文記錄了Android多用戶場景下子用戶系統(tǒng)應(yīng)用無法獲取MAC地址的問題及其解決方案,問題在于bionic層ifaddrs.cpp中對UID判斷的邏輯錯誤,文中詳細(xì)分析了問題原因及各種解決方案的優(yōu)缺點(diǎn),需要的朋友可以參考下

一、前言

最近在做 Android 多用戶功能適配時,發(fā)現(xiàn)一個問題:

在主用戶下可以正常獲取有線網(wǎng)(Ethernet)的 MAC 地址,但切換到子用戶后,通過 `NetworkInterface.getHardwareAddress()` 獲取到的 MAC 地址為 null。
獲取mac地址,需要系統(tǒng)應(yīng)用或者系統(tǒng)權(quán)限應(yīng)用;普通應(yīng)用是獲取不到的;
目前問題是系統(tǒng)權(quán)限應(yīng)用,在子用戶下也是無法獲取到有線網(wǎng)節(jié)點(diǎn)eth0的mac地址。

這個問題影響了子用戶下的網(wǎng)絡(luò)信息展示、設(shè)備標(biāo)識、某些應(yīng)用激活等功能。

經(jīng)過分析,問題出在 bionic 庫的 ifaddrs.cpp 中,對多用戶場景的 UID 判斷代碼。

本文記錄完整的分析過程和解決方案。

Android13 之后好像就有這個問題,本文的代碼具體代碼展示是Android16的。

二、問題現(xiàn)象

在 Android 設(shè)備上創(chuàng)建子用戶(userId=10)后,子用戶中的系統(tǒng)應(yīng)用調(diào)用以下代碼獲取 MAC 地址:

NetworkInterface ni = NetworkInterface.getByName("eth0");
byte[] mac = ni.getHardwareAddress();  // 子用戶下返回 null
場景結(jié)果
主用戶(userId=0)系統(tǒng)應(yīng)用? 正常返回 MAC 地址
主用戶(userId=0)普通應(yīng)用? 返回 null(正常,安全限制)
子用戶(userId=10)系統(tǒng)應(yīng)用? 返回 null(異常,本文要解決的問題
子用戶(userId=10)普通應(yīng)用? 返回 null(正常,安全限制)

三、原因分析

1、NetworkInterface.getHardwareAddress() 調(diào)用鏈路

從應(yīng)用層到內(nèi)核層的完整調(diào)用鏈路如下:

應(yīng)用層: NetworkInterface.getHardwareAddress()
  ↓
Java 層: libcore/ojluni/src/main/java/java/net/NetworkInterface.java
  ↓  返回 ni.hardwareAddr 字段
JNI 層: libcore/luni/src/main/native/libcore_io_Linux.cpp
  ↓  調(diào)用 getifaddrs()
Bionic 層: bionic/libc/bionic/ifaddrs.cpp  ← 【問題所在】
  ↓  通過 netlink 發(fā)送 RTM_GETLINK 請求
內(nèi)核層: netlink socket → 返回 AF_PACKET 類型地址(包含 MAC)

getHardwareAddress() 的 Java 層代碼本身沒有做 UID 權(quán)限判斷,它只是返回 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;  // 關(guān)鍵:這個值來自 native 層
}

hardwareAddr 的值是在 native 層通過 getifaddrs() 獲取并填充的。如果 getifaddrs() 沒有返回 AF_PACKET 類型的地址信息,hardwareAddr 就是 null。

2、getifaddrs() 中的 UID 判斷邏輯

問題的根源在 bionic/libc/bionic/ifaddrs.cpp 中的 getifaddrs() 函數(shù):

// bionic/libc/bionic/ifaddrs.cpp
int getifaddrs(ifaddrs** out) {
  *out = nullptr;
  NetlinkConnection nc;
  // 關(guān)鍵判斷:只有 uid < 10000 的進(jìn)程才發(fā)送 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:獲取網(wǎng)絡(luò)接口的鏈路層信息(包含 MAC 地址),只對 uid < 10000 的進(jìn)程發(fā)送
  • RTM_GETADDR:獲取網(wǎng)絡(luò)接口的 IP 地址信息,所有進(jìn)程都可以發(fā)送

注釋也說明了原因:SELinux policy only allows RTM_GETLINK messages to be sent by system apps。

3、Android 多用戶 UID 計(jì)算規(guī)則

Android 多用戶下,UID 的計(jì)算公式為:

uid = userId * 100000 + appId

其中:

  • userId:用戶 ID,主用戶為 0,子用戶從 10 開始
  • appId:應(yīng)用 ID,系統(tǒng)進(jìn)程 < 10000,普通應(yīng)用 ≥ 10000
  • 100000:用戶偏移量(AID_USER_OFFSET

各場景下的 UID 值:

場景userIdappIdgetuid() 返回值
主用戶 system_server010001000
主用戶 shell020002000
主用戶普通應(yīng)用01006810068
子用戶 system1010001001000
子用戶 shell1020001002000
子用戶普通應(yīng)用10100681010068

4、問題根因定位

原代碼的判斷條件:

if (getuid() < FIRST_APPLICATION_UID)  // 即 getuid() < 10000
  • 主用戶 system(uid=1000):1000 < 10000 ? → 發(fā)送 RTM_GETLINK → 獲取到 MAC
  • 子用戶 system(uid=1001000):1001000 < 10000 ? → 不發(fā)送 RTM_GETLINK → MAC 為 null

問題根因:ifaddrs.cpp 使用完整的 uid 做判斷,沒有考慮多用戶場景。子用戶的系統(tǒng)進(jìn)程 uid 遠(yuǎn)大于 10000,被錯誤地當(dāng)作普通應(yīng)用處理,導(dǎo)致 RTM_GETLINK 請求不會發(fā)送,MAC 地址無法獲取。

四、解決方案

1、修改NetworkInterface.getHardwareAddress()的返回信息 ?

這個是肯定不行的。

因?yàn)镹etworkInterface 的代碼位置在 libcore/ojluni/src/main/java/java/net/NetworkInterface.java。

這個是Java的類包,無法導(dǎo)入Android的類,獲取不到Android的信息。

并且這個類庫不是隨系統(tǒng)編譯的,試過代碼中加入Java打印,編譯驗(yàn)證是沒有的顯示的,

估計(jì)要用特殊指令單獨(dú)編譯這塊代碼,才會更新系統(tǒng)相關(guān)依賴包。

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
  // 子用戶的系統(tǒng)應(yīng)用 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)的固定值,從未改變過。通過取模運(yùn)算提取出 appId,就能正確識別所有用戶下的系統(tǒng)進(jìn)程。

修改前后效果對比

場景uid原邏輯 getuid() < 10000修改后 getuid() % 100000 < 10000
主用戶 system1000? 發(fā)送 RTM_GETLINK? 發(fā)送
主用戶普通應(yīng)用10068? 不發(fā)送? 不發(fā)送
子用戶 system1001000? 不發(fā)送(問題)? 發(fā)送(appId=1000)
子用戶普通應(yīng)用1010068? 不發(fā)送? 不發(fā)送(appId=10068)

修改后,子用戶的系統(tǒng)應(yīng)用可以正常獲取 MAC 地址。

普通應(yīng)用可以嗎?測試了一下還是不行!

就算強(qiáng)制進(jìn)入獲取 getlink_success 的邏輯,普通應(yīng)用還是會返回0;

估計(jì)還要適配系統(tǒng)其他權(quán)限問題,比較麻煩所以這個解決方案對普通應(yīng)用不行。

并且這種修改對 EDLA 認(rèn)證可能會有影響,不建議使用。

3、應(yīng)用層替代方案

如果不方便修改 bionic 層,也可以在應(yīng)用層(系統(tǒng)應(yīng)用)通過讀取 sysfs 文件來獲取 MAC 地址:

/**
 * 通過 sysfs 獲取有線網(wǎng) 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 策略允許應(yīng)用讀取 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版本確實(shí)可以通過cat sys/class/net/eth0/address 獲取有線網(wǎng)mac地址

但是配置策略比較麻煩,有需要的可以自行測試。

wifi的mac地址同理:sys/class/net/wlan0/address

這個方案也是只能適配系統(tǒng)應(yīng)用,并且要適配權(quán)限,比較麻煩,不建議修改。

4、主線程的服務(wù)/應(yīng)用獲取并記錄mac地址

可以在主用戶進(jìn)程中獲取 MAC 地址寫入個系統(tǒng)屬性,子用戶直接讀屬性:

String mac= getMacFromNetworkInterface();//主用戶可以拿到
SystemProperties.set("persist.debug.eth.mac",mac);
// 子用戶應(yīng)用中,非系統(tǒng)應(yīng)用需要反射獲取
String mac=SystemProperties.get("persist.debug.etho.mac","");

可以在系統(tǒng)wifi服務(wù)或者自定義的系統(tǒng)應(yīng)用服務(wù)啟動時獲取mac,切換用戶過程,這些服務(wù)是一直在的。

這方案修改代碼最少,又不影響系統(tǒng)其他功能。

如果不行用prop屬性,是否可以用Settings屬性?

一般的Settings.System、Secure都時候會重置的,Global屬性不會重置,這個獲取和設(shè)置更加簡單一點(diǎn)。

//系統(tǒng)服務(wù)
Settings.Global.putString(getContentResolver(), "mac_address", macStringXXX);
//普通應(yīng)用,直接調(diào)用,不用反射
String deviceMac = Settings.Global.getString(getContentResolver(),"mac_address");

這個是目前最簡單的實(shí)現(xiàn)方式,Settings.Global 保存和獲取mac地址信息;

普通應(yīng)用是沒有Settings設(shè)置權(quán)限的,只有讀取權(quán)限。

5、讓普通用戶也可以設(shè)置和獲取Settings.Global 屬性

這個需要修改framework的代碼,也是不太建議的。

系統(tǒng)修改下面兩個地方其中一個:

DefaultPermissionGrantPolicy.java → 給指定包名自動授權(quán)
	grantPermissionsToPackage
PermissionManagerService.java → 全局放行權(quán)限(所有應(yīng)用都能用)
	private boolean checkPermission(String perm, int pid, int uid, boolean debug)

普通應(yīng)用定義權(quán)限:

android.permission.WRITE_SECURE_SETTINGS
android.permission.WRITE_GLOBAL_SETTINGS

之前普通應(yīng)用就可以通過Settings.Global.putString 和 Settings.Global.getString 設(shè)置獲取屬性;

但是這個是破壞Android安全性的,EDLA認(rèn)證是會有報(bào)錯的。

五、其他

1、小結(jié)

Android 切換用戶后無法獲取 MAC 地址的根本原因是 bionic/libc/bionic/ifaddrs.cpp 中的 getifaddrs() 函數(shù)使用完整的 uid 做權(quán)限判斷,沒有考慮多用戶場景。

子用戶的系統(tǒng)進(jìn)程 uid(如 1001000)遠(yuǎn)大于 FIRST_APPLICATION_UID(10000),被錯誤地當(dāng)作普通應(yīng)用,導(dǎo)致 RTM_GETLINK 請求不會發(fā)送,NetworkInterface.getHardwareAddress() 返回 null。

解決方案是將 getuid() 改為 getuid() % 100000,提取 appId 后再做判斷,這樣所有用戶下的系統(tǒng)進(jìn)程都能正確獲取 MAC 地址,同時不影響普通應(yīng)用的安全限制。

修改方式和修改涉及的文件:

  • bionic 層bionic/libc/bionic/ifaddrs.cpp(核心修改,改一行代碼)
  • SELinux 策略:如有需要,確保子用戶的系統(tǒng)應(yīng)用有 netlink_route_socket 權(quán)限
  • 應(yīng)用層替代:可通過讀取 /sys/class/net/eth0/address 繞過,需配置 SELinux 策略
  • 系統(tǒng)服務(wù):系統(tǒng)服務(wù)啟動時獲取mac地址保存到prop屬性或者Settings.Global ,子用戶可以讀取。
  • 目前驗(yàn)證測試,通過系統(tǒng)服務(wù)設(shè)置Settings.Global 的mac屬性,普通用戶獲取 Settings.Global 是最簡單的。

2、獲取ip和mac地址幾種方式

1、ifconfig

2、獲取wifi 的ip
可以通過 WifiManager 獲取當(dāng)前連接的wifi信息,獲取到ip地址;

3、獲取有線網(wǎng)、wifi的ip、mac
通過獲取 ConnectivityManager獲取連接的網(wǎng)絡(luò) Network-->LinkProperties獲取到ip地址。

4、獲取有線網(wǎng)、wifi、熱點(diǎn)、p2p的ip、mac
通過獲取所有節(jié)點(diǎn)信息:NetworkInterface.getNetworkInterfaces() 獲取對應(yīng)的ip地址和MAC地址。

3、普通應(yīng)用反射獲取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);
        }
    }
    /**
     * 獲取系統(tǒng)屬性值
     *
     * @param key 屬性名,如 "ro.build.display.id"
     * @param defaultValue 默認(rèn)值,屬性不存在或無權(quán)限時返回
     * @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;
    }
    /**
     * 獲取系統(tǒng)屬性值,默認(rèn)返回空串
     */
    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;
        }
    }
    /**
     * 設(shè)置系統(tǒng)屬性(普通應(yīng)用通常沒有權(quán)限,僅系統(tǒng)應(yīng)用可用)
     */
    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);
        }
    }
}

如果系統(tǒng)應(yīng)用通過prop設(shè)置mac地址屬性,普通用戶就用上面這個封裝方法獲取prop的屬性。

以上就是Android切換用戶后無法獲取MAC地址的解決方法的詳細(xì)內(nèi)容,更多關(guān)于Android無法獲取MAC地址的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

都江堰市| 沁源县| 连江县| 博野县| 庆阳市| 察雅县| 高密市| 瑞安市| 林州市| 玛曲县| 称多县| 汨罗市| 双柏县| 余姚市| 广东省| 社会| 南乐县| 铜梁县| 福泉市| 平顺县| 抚州市| 综艺| 龙里县| 乐至县| 维西| 江西省| 临朐县| 平山县| 湘西| 扶余县| 秀山| 桃源县| 永嘉县| 旅游| 定南县| 肥东县| 吉水县| 榆社县| 陕西省| 平谷区| 师宗县|