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地址。
原作者:Terry.W