嵌入式linux修改网口MAC地址

Posted Yeah__binbin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌入式linux修改网口MAC地址相关的知识,希望对你有一定的参考价值。

使用带有网络功能的设备时,如果局域网内可能会连接很多台设备,为了保证网络通信正常,要确保每台设备的硬件MAC地址都不相同,一般在批量生产的时候可以根据产品序列号(sn号)固化每台设备的MAC地址,防止出现MAC冲突的问题。

修改MAC地址方法:
一、应用层
1.通过命令ifconfig修改:
ifconfig xxx(网口名) down
ifconfig xxx(网口名) hw ether xx:xx:xx:xx:xx:xx(MAC地址)
ifconfig xxx up(重启网卡)

如下图

2.通过linux的socket接口修改(上面命令行本质上也是这样实现的)
如下部分实现代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/route.h>


int fd = -1;
struct ifreq ifr;

fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0)

	printf("socket error\\n");

memset(&ifr,0,sizeof(ifr));
strcpy(ifr.ifr_name,eth_name);
setsockopt(fd,SOL_SOCKET,SO_BINDTODEVICE,(char*)&ifr,sizeof(ifr));
if(ioctl(fd,SIOCGIFHWADDR,&ifr) < 0)  //获得MAC地址

	printf("ioctl SIOCSIFHWADDR error\\n");

unsigned char mac[6]=12,23,34,45,56,67;
memcpy(ifr.ifr_hwaddr,mac,6);
if(ioctl(fd,SIOCSIFHWADDR,&ifr) < 0)  //设置MAC地址

	printf("ioctl SIOCSIFHWADDR error\\n");

在应用层修改MAC比较灵活方便,由app可随时设置和管理MAC地址。

二、kernel层修改
修改网卡的设备驱动,原理和下面uboot修改相似。在此不叙述。

三、uboot修改
kernel的网卡设备驱动最终是根据读网卡的MAC地址寄存器来向上层传递MAC地址的,因此在uboot修改相关寄存器也可以实现修改MAC地址。

这里以2019.01的uboot为例子。首先在uboot源码找到网络设备驱动的核心层,看看是怎么配置和初始化网络设备的。
uboot/ /net/eth-uclass.c (新的驱动基本使用DM框架,驱动核心层一般在xxx-uclass.c,老的在xxx_legacy.c一般不使用)。在网口eth_post_probe入口函数找到关于MAC地址的配置接口。

/* Check if the device has a MAC address in ROM */
if (eth_get_ops(dev)->read_rom_hwaddr)
	eth_get_ops(dev)->read_rom_hwaddr(dev);

eth_env_get_enetaddr_by_index("eth", dev->seq, env_enetaddr);
if (!is_zero_ethaddr(env_enetaddr)) 
	if (!is_zero_ethaddr(pdata->enetaddr) &&
	memcmp(pdata->enetaddr, env_enetaddr, ARP_HLEN)) 
		printf("\\nWarning: %s MAC addresses don't match:\\n",
		dev->name);
		printf("Address in ROM is %pM\\n",
		pdata->enetaddr);
		printf("Address in environment is %pM\\n",
		env_enetaddr);


/* Override the ROM MAC address */
memcpy(pdata->enetaddr, env_enetaddr, ARP_HLEN);
 else if (is_valid_ethaddr(pdata->enetaddr)) 
	eth_env_set_enetaddr_by_index("eth", dev->seq, pdata->enetaddr);
	printf("\\nWarning: %s using MAC address from ROM\\n",
	dev->name);
 else if (is_zero_ethaddr(pdata->enetaddr) ||
!is_valid_ethaddr(pdata->enetaddr)) 
#ifdef CONFIG_NET_RANDOM_ETHADDR
net_random_ethaddr(pdata->enetaddr);
printf("\\nWarning: %s (eth%d) using random MAC address - %pM\\n",
dev->name, dev->seq, pdata->enetaddr);
#else
printf("\\nError: %s address not set.\\n",
dev->name);
return -EINVAL;
#endif


可得出在uboot设置MAC地址大概流程:通过在外部存储介质(ROM)、环境变量env读取或随机生成MAC地址保存到匹配到的网卡外设的驱动数据dev->platdata->enetaddr(6个字节,16进制数格式存放)里,在后续uboot初始化硬件外设的时候调用向MAC控制器的相关寄存器写MAC地址的接口,实现将enetaddr写入硬件。这里有三种方法获得MAC地址:
1.若设备驱动有实现read_rom_hwaddr接口则执行,实现从ROM中读取MAC地址保存到设备的驱动数据eth_pdata结构体中。这里以zynq-7010平台、ROM为qspi flash W25q512为例,网口的设备驱动在uboot/drivers/net/zynq_gem.c里面实现。

static const struct eth_ops zynq_gem_ops = 
.start = zynq_gem_init,
.send = zynq_gem_send,
.recv = zynq_gem_recv,
.free_pkt = zynq_gem_free_pkt,
.stop = zynq_gem_halt,
.write_hwaddr = zynq_gem_setup_mac,
.read_rom_hwaddr = zynq_gem_read_rom_mac,
;

__weak int zynq_board_read_rom_ethaddr(unsigned char *ethaddr)

	return -ENOSYS;



static int zynq_gem_read_rom_mac(struct udevice *dev)

	struct eth_pdata *pdata = dev_get_platdata(dev);
	
	if (!pdata)
		return -ENOSYS;
	
	return zynq_board_read_rom_ethaddr(pdata->enetaddr);


*若有定义和实现int zynq_board_read_rom_ethaddr(unsigned char ethaddr
)则会调用该接口实现从rom读取MAC地址,否则执行上述末默认弱连接的接口。这里在
uboot/board/xilinx/zynq/board.c自定义该接口的实现。

int zynq_board_read_rom_ethaddr(unsigned char *ethaddr)

	int ret = 0;
	/*这里为了方便测试,手动设置MAC地址*/
	#if 1
	ethaddr[0]==0x00;
	ethaddr[1]==0xa8;
	ethaddr[2]==0x22;
	ethaddr[3]==0x12;
	ethaddr[4]==0x34;
	ethaddr[5]==0x56#endif
	/*实际使用下面接口读取spi flash特定区域的内容,可自由添加读写flash、emmc、epprom等ROM的接口实现在ROM读取mac地址*/
	#if 0 
	unsigned int mac_offset=0x210000;//在flash的0x210000存取MAC地址
	unsigned int len=6;
	ret = board_spi_flash_probe();  //匹配spi flash
	if (ret)
	
		printf("spi_flash_probe fail \\n");
		return -1;
	
	
	ret = spi_flash_read(board_spi_flash, mac_offset, len, ethaddr); //从spi flash
	if (ret)
	
		return -2;
	
	
	
	#endif
	return ret;


2.MAC地址从环境变量env中获得。在上面的eth_env_get_enetaddr_by_index(“eth”, dev->seq, env_enetaddr)接口实现获得MAC地址,这种方法需要在env中定义ethaddr=xx:xx:xx:xx:xx:xx。

int eth_env_get_enetaddr_by_index(const char *base_name, int index,
uchar *enetaddr)

	char enetvar[32];
	sprintf(enetvar, index ? "%s%daddr" : "%saddr", base_name, index);
	return eth_env_get_enetaddr(enetvar, enetaddr);


3.当上述都不能获得有效的MAC地址,可以使用net_random_ethaddr(pdata->enetaddr);根据定时器产生的随机数生成有效、随机的MAC地址。

static inline void net_random_ethaddr(uchar *addr)

	int i;
	unsigned int seed = get_timer(0);
	
	for (i = 0; i < 6; i++)
	addr[i] = rand_r(&seed);
	
	addr[0] &= 0xfe; /* clear multicast bit */
	addr[0] |= 0x02; /* set local assignment bit (IEEE802) */


正确获取到MAC地址后写入MAC控制器相关寄存器调用流程:
uboot/common/board_r.c的initr_net()->eth-uclass.c的eth_initialize()->eth-uclass.c的eth_write_hwaddr(dev);->设备驱动zynq_gem.c的zynq_gem_ops.write_hwaddr →最后通过zynq_gem_setup_mac实现写入MAC控制器的MAC地址寄存器的操作。

static int zynq_gem_setup_mac(struct udevice *dev)

	u32 i, macaddrlow, macaddrhigh;
	struct eth_pdata *pdata = dev_get_platdata(dev);
	struct zynq_gem_priv *priv = dev_get_priv(dev);
	struct zynq_gem_regs *regs = priv->iobase;
	
	/* Set the MAC bits [31:0] in BOT */
	macaddrlow = pdata->enetaddr[0];
	macaddrlow |= pdata->enetaddr[1] << 8;
	macaddrlow |= pdata->enetaddr[2] << 16;
	macaddrlow |= pdata->enetaddr[3] << 24;
	
	/* Set MAC bits [47:32] in TOP */
	macaddrhigh = pdata->enetaddr[4];
	macaddrhigh |= pdata->enetaddr[5] << 8;
	
	for (i = 0; i < 4; i++) 
	writel(0, &regs->laddr[i][LADDR_LOW]);
	writel(0, &regs->laddr[i][LADDR_HIGH]);
	/* Do not use MATCHx register */
	writel(0, &regs->match[i]);
	
	
	writel(macaddrlow, &regs->laddr[0][LADDR_LOW]);
	writel(macaddrhigh, &regs->laddr[0][LADDR_HIGH]);
	
	return 0;

寄存器结构体
/* Device registers */
struct zynq_gem_regs 
u32 nwctrl; /* 0x0 - Network Control reg */
u32 nwcfg; /* 0x4 - Network Config reg */
u32 nwsr; /* 0x8 - Network Status reg */
u32 reserved1;
u32 dmacr; /* 0x10 - DMA Control reg */
u32 txsr; /* 0x14 - TX Status reg */
u32 rxqbase; /* 0x18 - RX Q Base address reg */
u32 txqbase; /* 0x1c - TX Q Base address reg */
u32 rxsr; /* 0x20 - RX Status reg */
u32 reserved2[2];
u32 idr; /* 0x2c - Interrupt Disable reg */
u32 reserved3;
u32 phymntnc; /* 0x34 - Phy Maintaince reg */
u32 reserved4[18];
u32 hashl; /* 0x80 - Hash Low address reg */
u32 hashh; /* 0x84 - Hash High address reg */
#define LADDR_LOW 0
#define LADDR_HIGH 1
u32 laddr[4][LADDR_HIGH + 1]; /* 0x8c - Specific1 addr low/high reg */
u32 match[4]; /* 0xa8 - Type ID1 Match reg */
u32 reserved6[18];
#define STAT_SIZE 44
u32 stat[STAT_SIZE]; /* 0x100 - Octects transmitted Low reg */
u32 reserved9[20];
u32 pcscntrl;
u32 rserved12[36];
u32 dcfg6; /* 0x294 Design config reg6 */
u32 reserved7[106];
u32 transmit_q1_ptr; /* 0x440 - Transmit priority queue 1 */
u32 reserved8[15];
u32 receive_q1_ptr; /* 0x480 - Receive priority queue 1 */
u32 reserved10[17];
u32 upper_txqbase; /* 0x4C8 - Upper tx_q base addr */
u32 reserved11[2];
u32 upper_rxqbase; /* 0x4D4 - Upper rx_q base addr */
;

下图为MAC控制器的寄存器表

这里分别测试了从ROM读取和在env定义MAC地址的两种情况
1.在flash读取MAC地址(上述方法1)。重新编译、烧录UBOOT。系统启动后网卡配置如下,MAC已成功修改为00:A8:22:12:34:56如下图

2.在env镜像添加MAC地址00:0A:22:11:22:33。如下。重新编译生成env镜像存入flash中,然后重新启动。
如下图定义ethaddr环境变量属性

uboot启动时提示MAC定义冲突的警告,从之前的流程分析这里会优先使用env的MAC地址

系统启动后如下MAC地址为env定义的ethaddr,验证成功。

linux做主备bond,切换网卡后,MAC地址回发生变化吗?

centOS7,做了bond1,主备两块网卡虚拟成同一个主网卡的MAC地址,当主网卡down了之后,自动切换到备网卡,但是MAC地址不发生变化,正常吗?

正常的,网口绑定,mode 1下,两个网口的mac地址,会变成一个,切换到任意一个网口上都不会改变。这样在主备切换之后,网络拓扑影响较小。
而mode 0下,也就是负载均衡模式,两个网口各承担一半的负载,同时使用,所以它们的mac地址是不一样的。
网口绑定一共有7中模式,可以测试一下,横向比较。
参考技术A ? 侠骨柔肠? 真生

以上是关于嵌入式linux修改网口MAC地址的主要内容,如果未能解决你的问题,请参考以下文章

嵌入式Linux 使用iperf3同时压力测试4个网口 解决方案

Linux下读取默认MAC地址

Linux下读取默认MAC地址

linux做主备bond,切换网卡后,MAC地址回发生变化吗?

linux 重启网卡 Device eth0 has different MAC address than expected, ignoring 如何解决

linux C语言 取网口MAC地址 类型转换问题 详见内