Linux 网络协议栈开发代码分析篇之VLAN(一)—— vlan 功能模块分析

本文深入分析Linux 3.14版本的VLAN功能,包括相关数据结构如vlan_ethhdr、vlan_hdr、vlan_group,以及VLAN设备的添加、删除和配置流程。探讨了vlan_dev_hard_header、vlan_dev_hard_start_xmit等关键接口函数,并讨论了VLAN设备与socket模块的交互,以及在不同内核版本中VLAN处理的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

整理自:http://blog.youkuaiyun.com/lickylin/article/details/41832967

本文代码基于linux3.14

      Vlan即虚拟局域网,一个vlan能够模拟一个常规的交换网络,实现了将一个物理的交换机划分成多个逻辑的交换网络。而不同的vlan之间如果要进行通信就要通过三层协议来实现。

     在linux中vlan的配置使用vconfig,使用vconfig配置一个交换网络,大致的流程如下:


# vconfig add eth0 100

# ifconfig eth0.100 up
# brctl  addbr br1
# ifconfig br1 up
# brctl addif br1 eth0.100
# brctl addif br1 eth2

这样就实现了将从eth0 口进来的vlan id 为100的数据流与eth2放在了一个逻辑交换网络中。

下面开始分析linux vlan模块


一、相关的数据结构

1.1 struct vlan_ethhdr

     包含vlan头部的二层头部结构体

/**
 *	struct vlan_ethhdr - vlan ethernet header (ethhdr + vlan_hdr)
 *	@h_dest: destination ethernet address
 *	@h_source: source ethernet address
 *	@h_vlan_proto: ethernet protocol
 *	@h_vlan_TCI: priority and VLAN ID
 *	@h_vlan_encapsulated_proto: packet type ID or len
 */
struct vlan_ethhdr {
	unsigned char	h_dest[ETH_ALEN];
	unsigned char	h_source[ETH_ALEN];
	__be16		h_vlan_proto;
	__be16		h_vlan_TCI;
	__be16		h_vlan_encapsulated_proto;
};

1.2 struct vlan_hdr

      vlan头部关联的结构体

/*
 * 	struct vlan_hdr - vlan header
 * 	@h_vlan_TCI: priority and VLAN ID
 *	@h_vlan_encapsulated_proto: packet type ID or len
 */
struct vlan_hdr {
	__be16	h_vlan_TCI;
	__be16	h_vlan_encapsulated_proto;
};

1.3 struct vlan_group 

     该结构体主要是实现将一个real device关联的vlan device设备存放在vlan_devices_arrays,(该变量首先是一个数组,数组里的每一个指针都是一个二级指针)

struct vlan_group {
	unsigned int		nr_vlan_devs;
	struct hlist_node	hlist;	/* linked list */
	struct net_device **vlan_devices_arrays[VLAN_PROTO_NUM]
					       [VLAN_GROUP_ARRAY_SPLIT_PARTS];
};

     该变量也是比较重要的一个数据结构,可以保证一个real device针对每一个vlan id创建的vlan device都是唯一的。

     Vlan group是通过全局变量vlan_group_hash的hash链表链接在一起的,其定义如下:

static struct hlist_head vlan_group_hash[VLAN_GRP_HASH_SIZE];

      由于vlan device、real device都是使用的struct net_device变量,因此目前代码中,没有将vlan_group定义在net_device中,而是重新定义了一个全局的hash链表数组中。

      那么问题来了,可不可以在struct net_device中增加一个链表头指针,将该real device关联的vlan device设备都添加到该链表中呢?

      当然是可以的,这样做的话,在查找一个real device关联的所有vlan device时,其查找时间肯定比当前的查找时间要快的(因为不用进行hash相关的操作),但是每一个struct net_device变量都增加了一个链表指针,增加了对内存的使用,这个就需要在内存与性能之间做取舍了。

      相应的set与get的接口函数如下:

/*
功能:根据vlan id以及vlan_group变量,查找符合条件的vlan device
*/
static inline struct net_device *__vlan_group_get_device(struct vlan_group *vg,
							 unsigned int pidx,
							 u16 vlan_id)
{
	struct net_device **array;

	array = vg->vlan_devices_arrays[pidx]
				       [vlan_id / VLAN_GROUP_ARRAY_PART_LEN];
	return array ? array[vlan_id % VLAN_GROUP_ARRAY_PART_LEN] : NULL;
}

static inline struct net_device *vlan_group_get_device(struct vlan_group *vg,
						       __be16 vlan_proto,
						       u16 vlan_id)
{
	return __vlan_group_get_device(vg, vlan_proto_idx(vlan_proto), vlan_id);
}

/*
功能:根据vlan id 以及vlan group变量,在vlan_devices_arrays中增加相应的vlan设备
     由于是直接增加设备,没有进行存在性判断,因此在调用该函数之前,
	 应该调用vlan_group_get_device判断要添加的vlan设备是否已存在。
*/
static inline void vlan_group_set_device(struct vlan_group *vg,
					 __be16 vlan_proto, u16 vlan_id,
					 struct net_device *dev)
{
	struct net_device **array;
	if (!vg)
		return;
	array = vg->vlan_devices_arrays[vlan_proto_idx(vlan_proto)]
				       [vlan_id / VLAN_GROUP_ARRAY_PART_LEN];
	array[vlan_id % VLAN_GROUP_ARRAY_PART_LEN] = dev;
}

1.4 struct vlan_dev_info 

     (linux3.14中没有找到该结构体定义)

     vlan设备相关的信息结构体

     该结构体定义了vlan device相关的一些信息,包括输入/输出方向优先级映射、vlan id、关联的real device、性能统计相关的信息等。

     该结构相关的变量被定义在struct net_device的priv指针变量指向的内存空间中。

/**
 *	struct vlan_dev_info - VLAN private device data
 *	@nr_ingress_mappings: number of ingress priority mappings
 *	@ingress_priority_map: ingress priority mappings
 *	@nr_egress_mappings: number of egress priority mappings
 *	@egress_priority_map: hash of egress priority mappings
 *	@vlan_id: VLAN identifier
 *	@flags: device flags
 *	@real_dev: underlying netdevice
 *	@real_dev_addr: address of underlying netdevice
 *	@dent: proc dir entry
 *	@cnt_inc_headroom_on_tx: statistic - number of skb expansions on TX
 *	@cnt_encap_on_xmit: statistic - number of skb encapsulations on TX
 */
struct vlan_dev_info {
	unsigned int				nr_ingress_mappings;
	u32					ingress_priority_map[8];
	unsigned int				nr_egress_mappings;
	struct vlan_priority_tci_mapping	*egress_priority_map[16];

	u16					vlan_id;
	u16					flags;

	struct net_device			*real_dev;
	unsigned char				real_dev_addr[ETH_ALEN];

	struct proc_dir_e
<think>嗯,用户这次的问题是关于Linux物理网卡如何处理未打标签的VLAN帧。从技术深度来看,这属于网络协议栈内核驱动的专业领域,用户可能是系统管理员或网络工程师。用户特意强调了"physical NIC"和"untagged VLAN frames",说明ta需要的是底层处理机制而非配置命令。 回顾引用资料,虽然提供了思科设备的处理方式,但用户明确指向Linux系统。这里需要严格区分:思科交换机的行为属于网络设备处理逻辑,而Linux作为操作系统,其网卡驱动和内核协议的处理逻辑完全不同。 核心技术点有三个层次:首先是物理网卡驱动层对原始帧的处理,其次是内核协议VLAN标签的识别逻辑,最后是用户空间配置工具(如vconfig/ip命令)如何影响内核行为。其中最关键的是,当物理网卡收到未标记帧时,内核会根据网络接口的配置状态决定是否添加默认VLAN标签。 注意到用户提到"untagged VLAN frames"这个表述有微妙矛盾——严格来说未打标签的帧不属于任何VLAN。这里推测用户实际想问的是:当物理接口配置了VLAN子接口时,如何处理收到的普通以太网帧(无802.1Q标签)。 需要特别澄清Linux的两种VLAN处理模式:传统VLAN(vconfig)和802.1q VLAN子接口。后者正是引用[2]中提到的概念,但实现方式与思科设备不同。Linux内核会为每个VLAN子接口创建虚拟网络设备(如eth0.10),而物理接口本身不处理VLAN标签。 最后生成的三个问题都围绕不同层级的处理机制:问题1聚焦驱动层行为,问题2关注内核与用户空间的配置关系,问题3则涉及实际应用场景。这样设计既能满足当前查询的深度,又为后续技术探讨留下空间。</think>在Linux系统中,物理网卡(NIC)对未打标签的VLAN帧的处理逻辑如下: ### 处理流程 1. **帧接收** 物理网卡接收到以太网帧时,首先检查其**802.1Q标签**(VLAN Tag): - 若帧**含VLAN标签**:交由内核的802.1Q模块处理,根据VLAN ID匹配对应的VLAN子接口(如`eth0.10`)。 - 若帧**无VLAN标签**:视为**Native VLAN**(默认VLAN)流量,直接传递给物理接口(如`eth0`)关联的网络[^2]。 2. **Native VLAN配置** 物理接口的Native VLAN由用户空间工具(如`vconfig`或`ip`命令)配置: ```bash # 设置eth0的Native VLANVLAN 10 ip link add link eth0 name eth0.10 type vlan id 10 ``` 此时: - 接收端:所有无标签帧由`eth0`处理,并自动关联到Native VLANVLAN 10)[^2]。 - 发送端:从`eth0.10`发出的帧会被**剥离VLAN标签**再经物理网卡发出。 3. **内核协议处理** - 无标签帧直接进入物理接口的**内核网络协议栈**。 - 若物理接口未配置IP地址,仅VLAN子接口有IP,则无标签帧**无法被路由**,通常被丢弃或需额外过滤规则处理。 ### 关键机制对比 | **场景** | **接收处理** | **发送处理** | |------------------------|------------------------------------------|------------------------------------------| | **带标签帧** (VLAN 20) | 路由到对应VLAN子接口 (如`eth0.20`) | 从子接口发出,保留VLAN标签 | | **无标签帧** | 由物理接口处理,关联Native VLAN | 从物理接口发出无标签帧 | | **未配置Native VLAN** | 无标签帧仍由物理接口处理,但无VLAN关联 | 物理接口直接发送原始帧 | ### 示例配置 ```bash # 创建VLAN子接口 (VLAN 10为Native VLAN) ip link add link eth0 name eth0.10 type vlan id 10 # 配置IP地址 ip addr add 192.168.1.1/24 dev eth0.10 # 启用接口 ip link set eth0.10 up ``` 此时: - 接收无标签帧 → 由`eth0.10`处理(IP:192.168.1.1) - 接收带VLAN 10标签帧 → 同样由`eth0.10`处理 - 接收带VLAN 20标签帧 → 需额外创建`eth0.20`才能处理 > 📌 **注意**:若物理接口本身配置了IP(如`eth0`有IP地址),无标签帧会被该IP层处理,可能导致与VLAN子接口的IP冲突,因此通常建议**物理接口不配置IP**,仅作为VLAN trunk接口使用[^2]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值