RK3568 Android12 MAC地址生成简析

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(&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);
        } 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);

根据参考资料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地址定义有什么特殊要求? ↩︎

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值