Linux 网络协议栈开发代码分析篇之VLAN—— VLAN收发处理
Posted zqixiao_09
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 网络协议栈开发代码分析篇之VLAN—— VLAN收发处理相关的知识,希望对你有一定的参考价值。
转自:http://blog.chinaunix.net/uid/28541347/year-201606-list-1.html
VLAN报文格式
基于802.1Q的VLAN帧格式如下:
Type:长度为2字节,取值为0x8100,表示此帧的类型为802.1Q Tag帧。
PRI:长度为3比特,可取0~7之间的值,表示帧的优先级,值越大优先级越高。该优先级主要为QoS差分服务提供参考依据(COS)。
VLAN Identifier (VID) : 长度12bits,可配置的VLAN ID取值范围为1~4094。通常vlan 0和vlan 4095预留,vlan1为缺省vlan,一般用于网管
注意:这里的两个Type,前面802.1Q Tag中的Type,指明这个是VLAN报文,其值为0x8100;而对于后面Length/Type中的Type指定的是以太网内层协议的类型,如IP或ARP等。
相关数据结构
1.1 struct vlan_ethhdr
包含vlan头部的二层头部结构体
点击(此处)折叠或打开
- struct vlan_ethhdr
- unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
- unsigned char h_source[ETH_ALEN]; /* source ether addr */
- __be16 h_vlan_proto; /* Should always be 0x8100 */
- __be16 h_vlan_TCI; /* Encapsulates priority and VLAN ID */
- __be16 h_vlan_encapsulated_proto; /* packet type ID field (or len) */
- ;
1.2 struct vlan_hdr
vlan头部关联的结构体
点击(此处)折叠或打开
- struct vlan_hdr
- __be16 h_vlan_TCI; /* Encapsulates priority and VLAN ID */
- __be16 h_vlan_encapsulated_proto; /* packet type ID field (or len) */
- ;
不支持VLAN的网卡
对于不支持VLAN的网卡,也就不能识别报文中Type为0x8100这个类型有什么特殊之处,网卡驱动会将其当作普通mac帧收上来。注意此时,如果是正常的mac帧(非VLAN),skb->protocol会被设置成mac帧的第13、14字节,也就是(Length/Type)中的Type,而对于VLAN的mac帧来说同样会被设置为mac帧的第13、14字节,但此时是802.1Q Tag中的Type(至于为什么,看下VLAN的格式就明白了)。
所以对于不支持VLAN的网卡收到VLAN mac帧后,skb->protocol是等于0x8100的。有了这个背景再看下面的处理逻辑。
首先,无论什么数据包通过网卡驱动后都会进入netif_receive_skb函数。
下面看netif_receive_skb函数,其中已经出去和VLAN接收的无关逻辑。
int netif_receive_skb(struct sk_buff *skb)
struct packet_type *ptype, *pt_prev;
//这里是重点,但是只有网卡支持VLAN时才会设置skb->vlan_tci
if (skb->vlan_tci && vlan_hwaccel_do_receive(skb))
return NET_RX_SUCCESS;
//……
//遍历ptye_all链表, 上面的paket_type.type 为 ETH_P_ALL,
list_for_each_entry_rcu(ptype, &ptype_all, list)
if (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
ptype->dev == orig_dev)
if (pt_prev)//注意,此时orig_dev为物理dev,如eth0
// 此函数最终调用paket_type.func()
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
//bridge逻辑(可以看到bridge逻辑再VLAN处理之前)
skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
//这里和VLAN没有关系,而是mac-vlan的相关功能,编译内核时选上MAC_VLAN模块,下面才会执行
skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
//这里的type被置为VLAN协议,即0x8100
type = skb->protocol;
//处理ptype_base[ntohs(type)&15]上的所有的 packet_type->func()
list_for_each_entry_rcu(ptype,
&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list)
if (ptype->type == type &&
(ptype->dev == null_or_orig || ptype->dev == skb->dev ||
ptype->dev == orig_dev))
if (pt_prev)
//此函数最终调用paket_type.func(),由于type为802.1Q的协议,所以会调用其对应的协议处理函数。
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
//……
在加载8021q时会注册相应packet_type,同时初始化相关处理函数func。
static struct packet_type vlan_packet_type __read_mostly =
.type = cpu_to_be16(ETH_P_8021Q),
.func = vlan_skb_recv, /* VLAN receive method */
;
所以接下来会调用vlan_skb_recv函数。
net/8021q/vlan_dev.c
l vlan_skb_recv
int vlan_skb_recv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *ptype, struct net_device *orig_dev)
struct vlan_hdr *vhdr;
struct net_device_stats *stats;
u16 vlan_id;
u16 vlan_tci;
/* skb_share_check()会调用3个函数:skb_sharde(), skb_clone(), kfree_skb(),都很重要。skb_shared()检查skb->users数目是否为1,不为1则表示有多个协议栈模块要处理它,此时就需要使用skb_clone()来复制一份skb;kfree_skb()并不一定释放skb,只有当skb->users为1时,才会释放;否则只是递减skb->users。*/
skb = skb_share_check(skb, GFP_ATOMIC);
if (skb == NULL)
goto err_free;
// VLAN_HLEN的值为4
if (unlikely(!pskb_may_pull(skb, VLAN_HLEN)))
goto err_free;
//从skb中获取到vlan_id
vhdr = (struct vlan_hdr *)skb->data;
vlan_tci = ntohs(vhdr->h_vlan_TCI);
vlan_id = vlan_tci & VLAN_VID_MASK;
rcu_read_lock();
//这一步是核心,此时skb->dev为真正的设备,经过vlan处理后,报文应该被上层协议看作是由vlan虚拟设备接收的,因此这里设置skb->dev为虚拟的vlan设备。
skb->dev = __find_vlan_dev(dev, vlan_id);//如何找到相应虚拟vlan设备后面分析
//更新设备统计计数
stats = &skb->dev->stats;
stats->rx_packets++;
stats->rx_bytes += skb->len;
//更新校验和,此时data指向了真正的数据字段,如ip或arp头
skb_pull_rcsum(skb, VLAN_HLEN);
skb->priority = vlan_get_ingress_priority(skb->dev, vlan_tci);
vlan_set_encap_proto(skb, vhdr); //重新设置skb->protocol
skb = vlan_check_reorder_header(skb); //去掉报文中的VLAN tag
netif_rx(skb); //再次送回协议栈
rcu_read_unlock();
return NET_RX_SUCCESS;
err_unlock:
rcu_read_unlock();
err_free:
kfree_skb(skb);Linux 网络协议栈开发代码分析篇之数据收发 —— netif_receive_skb()函数
Linux 网络协议栈开发代码分析篇之数据收发 —— dev_queue_xmit()函数
Linux从青铜到王者第十八篇:Linux网络基础第二篇之TCP协议