内核中为网络设备定义了5中MAC地址类型,如下所示,其中NETDEV_HW_ADDR_T_SLAVE类型目前没有使用。局域网LAN类型与存储SAN类型的MAC地址保存在net_device结构体的dev_addrs链表中;UNICAST与MULTICAST类型的MAC地址分别保存在uc和mc链表中。
#define NETDEV_HW_ADDR_T_LAN 1 // Local Area Network
#define NETDEV_HW_ADDR_T_SAN 2 // Storage Area Network
#define NETDEV_HW_ADDR_T_SLAVE 3 //
#define NETDEV_HW_ADDR_T_UNICAST 4 // Unicast
#define NETDEV_HW_ADDR_T_MULTICAST 5 // Multicast
struct net_device {
struct netdev_hw_addr_list uc;
struct netdev_hw_addr_list mc;
struct netdev_hw_addr_list dev_addrs;
unsigned char *dev_addr;
unsigned char broadcast[MAX_ADDR_LEN];
}
以上我们已看到有这些MAC地址存在,在使用ifconfig或者ip link show命名时,显示的设备MAC地址是哪个呢?由函数rtnl_fill_ifinfo可知,其取值为net_device结构体的dev_addr变量(单播MAC)和broadcast变量(广播MAC)的值。另外在驱动程序中也会使用到MAC地址dev_addr,用于判断接收数据包是否为到本机。 这么看来好像dev_addr/boradcast地址与之前的三个MAC地址链表并没有关系。以下看看硬件地址的初始化过程,了解下二者的关联。
static int rtnl_fill_ifinfo(...)
{
if (nla_put(skb, IFLA_ADDRESS, dev->addr_len, dev->dev_addr) ||
nla_put(skb, IFLA_BROADCAST, dev->addr_len, dev->broadcast))
goto nla_put_failure;
}
__be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)
{
if (unlikely(!ether_addr_equal_64bits(eth->h_dest, dev->dev_addr)))
skb->pkt_type = PACKET_OTHERHOST;
}
硬件地址初始化
在网络设备初始化时,初始化三类MAC地址:分别为局域网(LAN)MAC地址、单播(UNICAST)MAC地址和多播(MULTICAST)MAC地址。
struct net_device *alloc_netdev_mqs(...)
{
dev_addr_init(dev);
dev_mc_init(dev);
dev_uc_init(dev);
}
由硬件地址链表dev_addrs初始化函数可见,dev_addr其实是指向了dev_addrs地址链表中的第一个MAC地址,此地址就是我们在应用层使用IP命令查看网络设备信息时显示的单播MAC地址。对于广播MAC地址,内核在以太网设备创建时,将其值设置为全F,由函数eth_broadcast_addr实现。
在初始化dev_addrs链表时,首先加入了一个全0的MAC地址,因此dev_addr目前为全0的MAC。在驱动初始化时,例如e1000网卡驱动函数e1000_probe,其会从网卡的EEPROM中读取到真实的MAC地址,赋值到dev_addr中。
int dev_addr_init(struct net_device *dev)
{
__hw_addr_init(&dev->dev_addrs);
memset(addr, 0, sizeof(addr));
err = __hw_addr_add(&dev->dev_addrs, addr, sizeof(addr), NETDEV_HW_ADDR_T_LAN);
if (!err) {
ha = list_first_entry(&dev->dev_addrs.list, struct netdev_hw_addr, list);
dev->dev_addr = ha->addr;
}
}
void ether_setup(struct net_device *dev)
{
eth_broadcast_addr(dev->broadcast);
}
存储SMAC地址
对于INTEL网卡驱动IXGBE来说,其会通过函数ixgbe_get_san_mac_addr_generic获取EEPROM中的SAN MAC地址,之后添加到net_device的dev_addrs链表中,类型为NETDEV_HW_ADDR_T_SAN。在其后FCoE创建接口时,会从dev_addrs链表中能够取出SAN类型的MAC地址使用。
static int fcoe_interface_setup(struct fcoe_interface *fcoe, struct net_device *netdev)
{
for_each_dev_addr(real_dev, ha)
if ((ha->type == NETDEV_HW_ADDR_T_SAN) && (is_valid_ether_addr(ha->addr))) {
memcpy(fip->ctl_src_addr, ha->addr, ETH_ALEN);
break;
}
}
单播MAC地址
单播MAC地址保存在net_device结构体的uc链表中,函数dev_uc_add负责添加单播MAC地址。由其在内核代码的调用可以看到目前vlan设备、macvlan设备、ipvlan设备与macsec设备等在添加单播MAC地址。
无论是单播MAC还是多播MAC都是用作MAC地址的接收过滤来使用,具体由函数__dev_set_rx_mode进行设置。将需要过滤的MAC地址下发到网驱动处理,例如e1000驱动中的e1000_set_rx_mode函数。但是,对于不支持单播过滤的网卡,有两种处理情况:1)如果存在MAC地址需要过滤,打开设备的混杂接收模式;2)如果没有MAC地址需过滤,关闭混杂模式。
非混杂模式下,仅接收目的MAC与net_device结构中dev_addr值相同的数据包。
int dev_uc_add(struct net_device *dev, const unsigned char *addr)
{
err = __hw_addr_add(&dev->uc, addr, dev->addr_len, NETDEV_HW_ADDR_T_UNICAST);
if (!err)
__dev_set_rx_mode(dev);
}
多播MAC地址
多播MAC地址保存在net_device结构体的mc链表中,由函数dev_mc_add负责添加多播MAC地址。与单播MAC地址相同,通过__dev_set_rx_mode函数可以设置网卡的多播地址接收过滤功能。
static int __dev_mc_add(struct net_device *dev, const unsigned char *addr, bool global)
{
err = __hw_addr_add_ex(&dev->mc, addr, dev->addr_len, NETDEV_HW_ADDR_T_MULTICAST, global, false, 0);
if (!err)
__dev_set_rx_mode(dev);
}
内核版本
Linux-4.15