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_ADDR
random_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;
}
#else
return -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 storage
if (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(ðaddr[id * ETH_ALEN])) {
dev_err(dev, "%s: rk_vendor_read eth mac address failed (%d)\n",
__func__, ret);
random_ether_addr(ðaddr[id * ETH_ALEN]);
memcpy(addr, ðaddr[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, ðaddr[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);
} else
crng_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);
根据参考资料5,get_random_bytes
是内核随机数产生器的输出接口,从理论上说这个随机数产生器产生的是真随机数。为了获得真正意义上的随机数,需要一个外部的噪声源。Linux内核找到了一个完美的噪声源产生者–就是使用计算机的人。我们在使用计算机时敲击键盘的时间间隔,移动鼠标的距离与间隔,特定中断的时间间隔等等,这些对于计算机来讲都是属于非确定的和不可预测的,这些不确定性可以通过驱动程序中注册的中断处理例程(ISR)获取。内核根据这些非确定性的设备事件维护着一个熵池,池中的数据是完全随机的。当有新的设备事件到来,内核会估计新加入的数据的随机性,当我们从熵池中取出数据时,内核会减少熵的估计值。
在获取随机数时,随机数是通过对熵池进行SHA哈希计算得到的。使用SHA哈希,是为了避免熵池的内部状态直接被外部获取,从而直接对后面的随机数进行预测6。
4. MAC地址重复问题
一般的说法是每个网络设备出厂都会有全球唯一的MAC地址,但是从实际经验来看我们大量采用随机生成的地址,并且还可以手动自定义修改地址,这显然没有唯一性。根据参考资料7,在要求不严格时MAC地址是可以自定义的,因为只要在一个子网内没有重复的MAC地址就不会造成地址冲突的问题。并且根据第3点的分析可知linux内核生成的随机数是理论上的真随机数,发生重复的概率是很低的,普通用户使用应该问题不大。但是如果要求比较严格的应用场景,则建议从IEEE基金会申请全球唯一的MAC地址。
如有谬误欢迎指正,感谢阅读~