一、引言
VLAN(虚拟局域网)在整车网络架构中起着至关重要的作用,它能够在物理网络基础设施上创建逻辑隔离的网络区域,提高车内网络的安全性、灵活性和性能。Linux 内核通过一系列复杂的机制实现了 VLAN 功能,本技术笔记将从源码分析的角度深入梳理 Linux VLAN 的实现原理,包括其数据结构和数据包的收发流程。
二、数据结构
(一) struct vlan_group
// 定义 VLAN 组结构体
struct vlan_group {
// 指向真实的网络设备,如物理网卡对应的设备结构体
struct net_device *real_dev;
// 用于组织该 VLAN 组内的 VLAN 设备的哈希链表头
struct hlist_head vlan_devices;
// 用于 RCU(读 - 拷贝更新)机制的结构体成员,用于并发控制
struct rcu_head rcu;
// 指向 VLAN 组的 RCU 相关数据结构,用于高效的内存管理和并发访问
struct vlan_group_rcu *vgr;
};
(二) struct vlan_dev_info
// 定义 VLAN 设备信息结构体
struct vlan_dev_info {
// 指向该 VLAN 设备对应的网络设备结构体
struct net_device *dev;
// 指向该 VLAN 设备所属的 VLAN 组结构体
struct vlan_group *vlan_group;
// 该 VLAN 设备的 VLAN ID
u16 vlan_id;
// 标志位,用于表示 VLAN 设备的一些属性或状态
u16 flags;
// 指向该 VLAN 设备对应的真实网络设备
struct net_device *real_dev;
// 用于入站数据包过滤的链表头,可设置过滤规则
struct list_head ingress_filter;
// 用于出站数据包过滤的哈希链表头,可设置过滤规则
struct hlist_head egress_filter;
// 用于 RCU 机制的结构体成员
struct rcu_head rcu;
};
这些数据结构是 Linux 实现 VLAN 功能的基础,通过它们可以有效地组织和管理 VLAN 设备以及 VLAN 组之间的关系。
三、接收流程
(一)入口函数 netif_receive_skb
// 网络数据包接收的关键入口函数
int netif_receive_skb(struct sk_buff *skb)
{
//... 其他代码省略
// 判断数据包的协议是否为 802.1Q(VLAN 协议)或 802.1AD(QinQ 协议)
if (skb->protocol == htons(ETH_P_8021Q) ||
skb->protocol == htons(ETH_P_8021AD)) {
// 如果是 VLAN 数据包,则调用 vlan_skb_recv 函数进行处理
return vlan_skb_recv(skb, skb->dev);
}
//... 其他代码省略
// 如果不是 VLAN 数据包,则返回接收成功的标识
return NET_RX_SUCCESS;
}
(二) vlan_skb_recv 函数
// 处理 VLAN 数据包接收的函数
int vlan_skb_recv(struct sk_buff *skb, struct net_device *dev)
{
struct vlan_group *vg;
struct vlan_dev_info *vlan;
u16 vid;
// 从数据包中获取 VLAN ID
vid = vlan_get_VID(skb);
// 通过 RCU 机制获取设备对应的 VLAN 组结构体
vg = rcu_dereference(dev->vlgrp);
// 如果没有对应的 VLAN 组,则丢弃该数据包
if (!vg)
return NET_RX_DROP;
// 根据 VLAN ID 在 VLAN 组中查找对应的 VLAN 设备信息结构体
vlan = vlan_group_get_device(vg, vid);
// 如果没有找到对应的 VLAN 设备,则丢弃该数据包
if (!vlan)
return NET_RX_DROP;
// 将数据包的接收设备设置为找到的 VLAN 设备
skb->dev = vlan->dev;
// 将数据包送入网络栈进行后续的接收处理
netif_receive_skb_internal(skb);
// 返回接收成功的标识
return NET_RX_SUCCESS;
}
在接收流程中,当带有 VLAN 标签的数据包到达网络接口时,首先在 netif_receive_skb 函数中被识别为 VLAN 数据包,然后交由 vlan_skb_recv 函数处理。 vlan_skb_recv 函数会解析数据包中的 VLAN ID,在对应的 VLAN 组中查找匹配的 VLAN 设备,并将数据包的接收设备设置为该 VLAN 设备后,继续进行后续的网络栈处理,就如同该数据包是直接从该 VLAN 设备对应的虚拟接口接收的一样。
四、发送流程
(一) dev_hard_start_xmit 函数及相关
在发送数据包时, dev_hard_start_xmit 函数(在 net/core/dev.c )会被调用,它是网络设备发送数据包的关键函数。对于 VLAN 设备,最终会调用到 vlan_dev_hard_start_xmit 函数。
(二) vlan_dev_hard_start_xmit 函数
// VLAN 设备发送数据包的函数
int vlan_dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct vlan_dev_info *vlan = netdev_priv(dev);
struct net_device *real_dev = vlan->real_dev;
u16 vlan_tci = vlan->vlan_id | VLAN_TAG_PRESENT;
// 将数据包指针向前移动 VLAN 头部长度,为添加 VLAN 标签腾出空间
skb_push(skb, VLAN_HLEN);
// 将 VLAN 标签(包含 VLAN ID 等信息)添加到数据包中
__vlan_put_tag(skb, htons(vlan_tci));
// 将数据包发送到真实设备的发送队列中进行发送
return dev_queue_xmit(skb);
}
在发送流程中,当应用程序通过系统调用将数据包发送到对应的 VLAN 设备(如 eth0.10 这样的 VLAN 接口)时, vlan_dev_hard_start_xmit 函数会被调用。它首先获取 VLAN 设备的配置信息,然后将 VLAN 标签添加到数据包中,最后通过底层的网络设备驱动将带有 VLAN 标签的数据包发送到物理网络中。
五、总结
Linux 通过精心设计的数据结构 struct vlan_group 和 struct vlan_dev_info ,以及在数据包收发流程中的关键函数 netif_receive_skb 、 vlan_skb_recv 、 dev_hard_start_xmit 和 vlan_dev_hard_start_xmit 等的协同工作,实现了 VLAN 功能。在接收时,能够根据 VLAN 标签将数据包正确地分发到对应的 VLAN 设备;在发送时,能够为数据包添加正确的 VLAN 标签并发送到物理网络。这种实现方式使得 Linux 系统在网络虚拟化和 VLAN 应用场景中具有很强的适应性和扩展性,为构建复杂的企业网络和云计算网络环境提供了坚实的基础。同时,由于涉及到内核代码和网络协议栈的底层操作,对于开发者来说,深入理解这些原理有助于进行网络性能优化、故障排查以及定制化的网络功能开发等工作。