Platform: RK3568
OS: Android 12
Kernel: v4.19.206
SDK Version:android-12.0-mid-rkr1
Module: MAC Address


问题

有客户提到一个问题,随机生成的MAC地址能否保证唯一不重复?
这个问题我一时半会答不上来,那就花点时间了解下。

简单分析

目前RK的MAC地址读取策略一般是1:优先使用烧写在 IDB 或 vendor Storage 中的 MAC 地址,若该地址符合规范,则使用。若不符合或没有
烧写,则随机生成 MAC 地址保存到 Vendor 分区中并使用,重启或恢复出厂设置不会丢失,但是完全擦除则会丢失。

Vendor Sotrage 是RK定义的一段保留分区23,用于存储SN,MAC,HDCP等用户数据,在uboot和kernel中都有提供接口用于操作。本文就只关注kernel部分,主要就是读写接口:
int rk_vendor_read(u32 id, void *pbuf, u32 size);
int rk_vendor_write(u32 id, void *pbuf, u32 size);

1. WIFI MAC地址

代码可见net/rfkill/rfkill-wlan.c

/**************************************************************************** Wifi MAC custom Func**************************************************************************/
#include <linux/etherdevice.h>
#include <linux/errno.h>
u8 wifi_custom_mac_addr[6] = { 0, 0, 0, 0, 0, 0 };//#define RANDOM_ADDRESS_SAVE
static int get_wifi_addr_vendor(unsigned char *addr)
{int ret;int count = 5;while (count-- > 0) {if (is_rk_vendor_ready())break;/* sleep 500ms wait rk vendor driver ready */msleep(500);}   ret = rk_vendor_read(WIFI_MAC_ID, addr, 6); if (ret != 6 || is_zero_ether_addr(addr)) {LOG("%s: rk_vendor_read wifi mac address failed (%d)\n",__func__, ret);
#ifdef CONFIG_WIFI_GENERATE_RANDOM_MAC_ADDRrandom_ether_addr(addr);LOG("%s: generate random wifi mac address: ""%02x:%02x:%02x:%02x:%02x:%02x\n",__func__, addr[0], addr[1], addr[2], addr[3], addr[4],addr[5]);ret = rk_vendor_write(WIFI_MAC_ID, addr, 6);if (ret != 0) {LOG("%s: rk_vendor_write failed %d\n"__func__, ret);memset(addr, 0, 6);return -1;}
#elsereturn -1;
#endif} else {LOG("%s: rk_vendor_read wifi mac address: ""%02x:%02x:%02x:%02x:%02x:%02x\n",__func__, addr[0], addr[1], addr[2], addr[3], addr[4],addr[5]);}return 0;
}
int rockchip_wifi_mac_addr(unsigned char *buf)
{char mac_buf[20] = { 0 };LOG("%s: enter.\n", __func__);// from vendor storageif (is_zero_ether_addr(wifi_custom_mac_addr)) {if (get_wifi_addr_vendor(wifi_custom_mac_addr) != 0)return -1;}sprintf(mac_buf, "%02x:%02x:%02x:%02x:%02x:%02x",wifi_custom_mac_addr[0], wifi_custom_mac_addr[1],wifi_custom_mac_addr[2], wifi_custom_mac_addr[3],wifi_custom_mac_addr[4], wifi_custom_mac_addr[5]);LOG("falsh wifi_custom_mac_addr=[%s]\n", mac_buf);if (is_valid_ether_addr(wifi_custom_mac_addr)) {if (!strncmp(wifi_chip_type_string, "rtl", 3))wifi_custom_mac_addr[0] &= ~0x2; // for p2p} else {LOG("This mac address is not valid, ignored...\n");return -1;}memcpy(buf, wifi_custom_mac_addr, 6);return 0;
}
EXPORT_SYMBOL(rockchip_wifi_mac_addr);

get_wifi_addr_vendor() 函数是实现对vendor storage分区读写wifi MAC地址的功能。先用rk_vendor_read(WIFI_MAC_ID, addr, 6)读取vendor storage分区中的wifi MAC地址,如果读不到或者地址格式不对的话,则用random_ether_addr(addr) 来生成随机MAC地址并通过rk_vendor_write(WIFI_MAC_ID, addr, 6);来回写到vendor storage分区中。

rockchip_wifi_mac_addr()函数主要是调用了get_wifi_addr_vendor()来获取MAC地址填充在buf的前6个位置,并开放给内核其他模块使用。比如在bcmdhd/dhd_gpio.c中:

static int dhd_wlan_get_mac_addr(unsigned char *buf)
{int err = 0;printf("======== %s ========\n", __FUNCTION__);
#ifdef EXAMPLE_GET_MAC/* EXAMPLE code */{struct ether_addr ea_example = {{0x00, 0x11, 0x22, 0x33, 0x44, 0xFF}};bcopy((char *)&ea_example, buf, sizeof(struct ether_addr));}
#endif /* EXAMPLE_GET_MAC */err = rockchip_wifi_mac_addr(buf);
……

2. Ethernet MAC地址

接下来看看以太网MAC地址,相关驱动位于drivers/net/ethernet/stmicro/stmmac/

dwmac-rk.c

void rk_get_eth_addr(void *priv, unsigned char *addr)
{struct rk_priv_data *bsp_priv = priv;struct device *dev = &bsp_priv->pdev->dev;unsigned char ethaddr[ETH_ALEN * MAX_ETH] = {0}; int ret, id = bsp_priv->bus_id;rk_devinfo_get_eth_mac(addr);if (is_valid_ether_addr(addr))goto out; if (id < 0 || id >= MAX_ETH) {dev_err(dev, "%s: Invalid ethernet bus id %d\n", __func__, id); return;}    ret = rk_vendor_read(LAN_MAC_ID, ethaddr, ETH_ALEN * MAX_ETH);if (ret <= 0 ||!is_valid_ether_addr(&ethaddr[id * ETH_ALEN])) {dev_err(dev, "%s: rk_vendor_read eth mac address failed (%d)\n",__func__, ret);random_ether_addr(&ethaddr[id * ETH_ALEN]);memcpy(addr, &ethaddr[id * ETH_ALEN], ETH_ALEN);dev_err(dev, "%s: generate random eth mac address: %pM\n", __func__, addr);ret = rk_vendor_write(LAN_MAC_ID, ethaddr, ETH_ALEN * MAX_ETH);if (ret != 0)dev_err(dev, "%s: rk_vendor_write eth mac address failed (%d)\n",__func__, ret);ret = rk_vendor_read(LAN_MAC_ID, ethaddr, ETH_ALEN * MAX_ETH);if (ret != ETH_ALEN * MAX_ETH)dev_err(dev, "%s: id: %d rk_vendor_read eth mac address failed (%d)\n",__func__, id, ret);} else {memcpy(addr, &ethaddr[id * ETH_ALEN], ETH_ALEN);}out:dev_err(dev, "%s: mac address: %pM\n", __func__, addr);
}static int rk_gmac_probe(struct platform_device *pdev)
{struct plat_stmmacenet_data *plat_dat;struct stmmac_resources stmmac_res;const struct rk_gmac_ops *data;int ret;
…………plat_dat = stmmac_probe_config_dt(pdev, &stmmac_res.mac);if (IS_ERR(plat_dat))return PTR_ERR(plat_dat);if (!of_device_is_compatible(pdev->dev.of_node, "snps,dwmac-4.20a"))plat_dat->has_gmac = true;plat_dat->fix_mac_speed = rk_fix_speed;plat_dat->get_eth_addr = rk_get_eth_addr;plat_dat->bsp_priv = rk_gmac_setup(pdev, plat_dat, data);

和wifi部分功能类似,rk_get_eth_addr也是实现了vendor storage分区的mac地址读写功能,通过调用random_ether_addr来生成随机地址的,然后在stmmac_main.c 中调用 priv->plat->get_eth_addr

3. 随机地址生成函数

random_ether_addr定义在include/linux/etherdevice.h

/*** eth_random_addr - Generate software assigned random Ethernet address* @addr: Pointer to a six-byte array containing the Ethernet address** Generate a random Ethernet address (MAC) that is not multicast* and has the local assigned bit set.*/
static inline void eth_random_addr(u8 *addr)
{get_random_bytes(addr, ETH_ALEN);addr[0] &= 0xfe;        /* clear multicast bit */addr[0] |= 0x02;        /* set local assignment bit (IEEE802) */
}#define random_ether_addr(addr) eth_random_addr(addr)

get_random_bytes(addr, ETH_ALEN);生成6个字节的随机数放到addr中。
根据MAC地址的规则4,将第一个字节的bit0设为0表示单播地址,bit1设为1表示本地地址。

get_random_bytes的定义在/drivers/char/random.c

/** This function is the exported kernel interface.  It returns some* number of good random numbers, suitable for key generation, seeding* TCP sequence numbers, etc.  It does not rely on the hardware random* number generator.  For random bytes direct from the hardware RNG* (when available), use get_random_bytes_arch(). In order to ensure* that the randomness provided by this function is okay, the function* wait_for_random_bytes() should be called and return 0 at least once* at any point prior.*/
static void _get_random_bytes(void *buf, int nbytes)
{__u8 tmp[CHACHA_BLOCK_SIZE] __aligned(4);trace_get_random_bytes(nbytes, _RET_IP_);while (nbytes >= CHACHA_BLOCK_SIZE) {extract_crng(buf);buf += CHACHA_BLOCK_SIZE;nbytes -= CHACHA_BLOCK_SIZE;}if (nbytes > 0) {extract_crng(tmp);memcpy(buf, tmp, nbytes);crng_backtrack_protect(tmp, nbytes);} elsecrng_backtrack_protect(tmp, CHACHA_BLOCK_SIZE);memzero_explicit(tmp, sizeof(tmp));
}void get_random_bytes(void *buf, int nbytes)
{static void *previous;warn_unseeded_randomness(&previous);_get_random_bytes(buf, nbytes);
}
EXPORT_SYMBOL(get_random_bytes);

根据参考资料5get_random_bytes是内核随机数产生器的输出接口,从理论上说这个随机数产生器产生的是真随机数。为了获得真正意义上的随机数,需要一个外部的噪声源。Linux内核找到了一个完美的噪声源产生者–就是使用计算机的人。我们在使用计算机时敲击键盘的时间间隔,移动鼠标的距离与间隔,特定中断的时间间隔等等,这些对于计算机来讲都是属于非确定的和不可预测的,这些不确定性可以通过驱动程序中注册的中断处理例程(ISR)获取。内核根据这些非确定性的设备事件维护着一个熵池,池中的数据是完全随机的。当有新的设备事件到来,内核会估计新加入的数据的随机性,当我们从熵池中取出数据时,内核会减少熵的估计值。
在获取随机数时,随机数是通过对熵池进行SHA哈希计算得到的。使用SHA哈希,是为了避免熵池的内部状态直接被外部获取,从而直接对后面的随机数进行预测6

4. MAC地址重复问题

一般的说法是每个网络设备出厂都会有全球唯一的MAC地址,但是从实际经验来看我们大量采用随机生成的地址,并且还可以手动自定义修改地址,这显然没有唯一性。根据参考资料7,在要求不严格时MAC地址是可以自定义的,因为只要在一个子网内没有重复的MAC地址就不会造成地址冲突的问题。并且根据第3点的分析可知linux内核生成的随机数是理论上的真随机数,发生重复的概率是很低的,普通用户使用应该问题不大。但是如果要求比较严格的应用场景,则建议从IEEE基金会申请全球唯一的MAC地址。


如有谬误欢迎指正,感谢阅读~

参考资料


  1. 《Rockchip Developer Guide Linux GMAC》 ↩︎

  2. [RK3399][Android7.1] Vendor Storage区域知识及探讨 ↩︎

  3. 《RK Vendor Storage Application Note》 ↩︎

  4. 百度百科:MAC地址 ↩︎

  5. 从Linux内核中获取真随机数 ↩︎

  6. Linux随机数发生器 ↩︎

  7. MAC地址定义有什么特殊要求? ↩︎

RK3568 Android12 MAC地址生成简析相关推荐

  1. Vue中使用uuidv1根据时间戳和MAC地址生成唯一标识

    场景 若依前后端分离版手把手教你本地搭建环境并运行项目: 若依前后端分离版手把手教你本地搭建环境并运行项目_霸道流氓气质的博客-CSDN博客_若依前后端分离搭建 在上面搭建起来Vue项目的基础上,怎样 ...

  2. QT使用AES加密MAC地址生成秘钥文件并存取

    1.下载QT第三方AES库 https://github.com/bricke/Qt-AES 2.拷贝头文件 将aesni下的头文件添加到工程的头文件中 将AES库文件下的qaesencryption ...

  3. 根据MAC地址生成软件的序列号

    下面是一个简单的生成liense的方法,通过机器的MAC地址,生成一个序列号. #include "stdafx.h" #include <stdio.h> #incl ...

  4. vmware虚拟机mac地址生成按钮为灰色怎么办?

    使用vmware虚拟机生成mac地址时发现按钮为灰色 尝试后发现,需要先将虚拟机关机,再点击编辑虚拟机设置->网络适配器->高级 这时发现可以生成mac地址了

  5. Java随机生成Mac地址(GitHub源码下载)

    生成随机 Mac 地址 这段时间做项目,涉及到虚拟机 Mac 地址生成,网上找了一些 Java 随机生成 Mac 地址的代码,大多都是从 {'0', '1', '2', '3', '4', '5', ...

  6. ble mac地址 协议_让BLE设备的名称包含MAC地址

    对于研发和测试BLE来说,经常看到同名的设备,是极为不方便的,一大堆设备同时上电会让同事不知道哪一个设备才是自己真正想操作的目标.再说一下小米手环,家中有三支小米手环,打开设备搜索全是"MI ...

  7. 克隆Centos之后,ifconfig得不到ip地址,mac地址冲突

    今天克隆了一个虚拟机,放到自己本机里面使用时,发现ifconfig得不到ip地址,很奇怪,网上找了很多资料,没什么结果,后面各种尝试终于解决.其实就是因为复制克隆的两个虚拟机的mac地址冲突了.所以一 ...

  8. 获取MAC地址的四种方法(转)

    https://www.cnblogs.com/zlshmily/p/10058560.html zlshmily 在实际的应用系统中,我们往往会需要在程序运行时获取当前机器的网卡的MAC地址,以便作 ...

  9. 记一次hook mac地址实现伪装硬件码

    1. 前言 好久没写文章了,工作比较忙,不过我还是对技术比较热爱,即使它不能给我带来利益,保持初心. 工作期间遇到一个问题,连接vpn的软件是校验机器硬件码,不是公司电脑不让使用vpn软件,上下班已经 ...

最新文章

  1. 看板管理大型项目-3.每日晨会
  2. python-实现单链表
  3. python3 set 集合 简介
  4. java抓新闻_【图片】【抓取】Java获取各大网站新闻【java吧】_百度贴吧
  5. indesign选中不了图片删除_(54)批量给图片加上说明文字
  6. MSSQL数据库C#操作类(SQLHELPER类的修改版)
  7. python发邮件给女朋友代码_python发邮件的代码
  8. 如何展示富文本_自助建站如何做出个性化效果?
  9. 在windows下安装Redis
  10. 分别用雅可比(Jacobi)迭代法和高斯—塞德尔(Gauss—Seidel)迭代法求解线性方程组(转载)
  11. Java并发编程中级篇(二):使用Semaphore信号量进行多个资源并发控制
  12. java Queue
  13. Nginx资源合并优化模块nginx-http-concat
  14. OSChina 周一乱弹 ——我后悔让爸妈用微信了!
  15. 3DSMAX和ZBRUSH打造神秘性感美女
  16. 如何将Matlab中命令窗口中的数据保存到一个文档中
  17. 【网络】Padavan 路由器固件开启教育网 IPv6
  18. 白杨SEO:企业口碑问答营销如何做?渠道选择、推广流程及注意事项
  19. 4.7 电源管理 第五部分 ---- Windows CE设备驱动开发之电源管理
  20. “AI+机器人”持续为多领域增“智”添“质”,开启效益增长飞轮

热门文章

  1. java实现链表冒泡排序_单链表的冒泡排序
  2. 笔者带你剖析淘宝TDDL(TAOBAO DISTRIBUTE DATA LAYER)
  3. GitHub 3.1K,业界首个流式语音合成系统开源!
  4. 西门子smart200PLC之间通讯
  5. notepad++添加HEX插件,编辑歌曲ID3信息,UTF8,UTF16 encoded Unicode,添加U2020字符方法
  6. 微信小程序调用豆瓣电影API(详细)
  7. 我收到一份《中国焦虑图鉴》
  8. 5G基站中的耗电量该如何控制
  9. 用c语言程序编写天干地支,农历中天干地支的计算【C代码】
  10. 【常用算法】螺旋矩阵