基于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等。
包含vlan头部的二层头部结构体
点击(此处)折叠或打开
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);
return NET_RX_DROP;
}
l vlan_set_encap_proto
static inline void vlan_set_encap_proto(struct sk_buff *skb,
struct vlan_hdr *vhdr)
{
__be16 proto;
unsigned char *rawp;
//根据VLAN的报文格式可知vhdr->h_vlan_encapsulated_proto就是真正以太网帧的类型,如IP,ARP
proto = vhdr->h_vlan_encapsulated_proto;
if (ntohs(proto) >= 1536) {
skb->protocol = proto;
return;
}
rawp = skb->data;
if (*(unsigned short *)rawp == 0xFFFF)
skb->protocol = htons(ETH_P_802_3);
else
skb->protocol = htons(ETH_P_802_2);
}
l vlan_check_reorder_header
static inline struct sk_buff *vlan_check_reorder_header(struct sk_buff *skb)
{
if (vlan_dev_info(skb->dev)->flags & VLAN_FLAG_REORDER_HDR) {
if (skb_cow(skb, skb_headroom(skb)) < 0)
skb = NULL;
if (skb) {
//这个是重点,ETH_HLEN=14,VLAN_ETH_HLEN=18
memmove(skb->data - ETH_HLEN, skb->data - VLAN_ETH_HLEN, 12);
skb->mac_header += VLAN_HLEN;// VLAN_HLEN=4
}
}
return skb;
}
执行memmove(skb->data - ETH_HLEN, skb->data - VLAN_ETH_HLEN, 12)前,报文内容如下:
执行后变为下图。
可见通过拷贝覆盖,将报文中的VLAN tag去掉了。然后执行skb->mac_header += VLAN_HLEN;
继续转发过程的分析,我们发下vlan_skb_recv最后调用了netif_rx(),进而又会进入到netif_receive_skb。有了bridge逻辑分析的基础,我们就不会奇怪为什么数据包转一圈又回来了。因为skb->dev已经变了,有物理设备(如eth0)变为了虚拟设备(如eth0.100),另外报文中的VLAN tag已经被抹去。所以同一个skb再次进入netif_receive_skb,和之前走的逻辑也是不同的。
注:netif_receive_skb()这个函数在报文接收中会多次进入的,网卡驱动收到报文进入netif_receive_skb(),bridge处理完后再进入netif_receive_skb(),vlan处理完成再进入netif_receive_skb()。而bridge处理完后会设置标志,表明bridge已经处理过该报文,在再次进入netif_receive_skb时就不会再被bridge模块处理。
下面总结一下不支持VLAN特性时的接收逻辑如下图:
说完了接收逻辑,再看下vlan的发送逻辑。我们知道数据包转发到vlan设备后,会调用vlan设备的.ndo_start_xmit函数,那么这个函数指针被初始化什么函数呢?这个函数是在vlan_dev_init中初始化的。
l vlan_dev_init
/net/8021q/vlan_dev.c
static int vlan_dev_init(struct net_device *dev)
{
//……
/*根据real device是否支持NETIF_F_HW_VLAN_TX,让vlan device的netdev_ops指针指向不同的接口函数。*/
if (real_dev->features & NETIF_F_HW_VLAN_TX) {
dev->header_ops = real_dev->header_ops;
dev->hard_header_len = real_dev->hard_header_len;
dev->netdev_ops = &vlan_netdev_accel_ops;
} else {
dev->header_ops = &vlan_header_ops;
dev->hard_header_len = real_dev->hard_header_len + VLAN_HLEN;
dev->netdev_ops = &vlan_netdev_ops;
}
//……
}
static const struct net_device_ops vlan_netdev_ops = {
//……
.ndo_start_xmit = vlan_dev_hard_start_xmit,
//……
}
所以真实设备不支持vlan时,发送或调用 vlan_dev_hard_start_xmit函数。
l vlan_dev_hard_start_xmit
static netdev_tx_t vlan_dev_hard_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
int i = skb_get_queue_mapping(skb);
struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
struct vlan_ethhdr *veth = (struct vlan_ethhdr *)(skb->data);
unsigned int len;
int ret;
//如果mac的协议类型不是vlan协议,说明还没有打上VLAN tag,则在此处添加上4字节的VLAN tag
if (veth->h_vlan_proto != htons(ETH_P_8021Q) ||
vlan_dev_info(dev)->flags & VLAN_FLAG_REORDER_HDR) {
unsigned int orig_headroom = skb_headroom(skb);
u16 vlan_tci;
vlan_dev_info(dev)->cnt_encap_on_xmit++;
vlan_tci = vlan_dev_info(dev)->vlan_id; //获取到vlan设备的vlan id
vlan_tci |= vlan_dev_get_egress_qos_mask(dev, skb);
skb = __vlan_put_tag(skb, vlan_tci);//在报文中添加VLAN tag
if (!skb) {
txq->tx_dropped++;
return NETDEV_TX_OK;
}
if (orig_headroom < VLAN_HLEN)
vlan_dev_info(dev)->cnt_inc_headroom_on_tx++;
}
//这里是重点,skb->dev设置为真实设备的dev
skb->dev = vlan_dev_info(dev)->real_dev;
len = skb->len;
ret = dev_queue_xmit(skb);//再次调用dev_queue_xmit
if (likely(ret == NET_XMIT_SUCCESS)) {
txq->tx_packets++;
txq->tx_bytes += len;
} else
txq->tx_dropped++;
return NETDEV_TX_OK;
}
我们知道dev_queue_xmit最终会调用skb->dev的.ndo_start_xmit,之前skb->dev指向的是vlan虚拟设备,调用虚拟设备的.ndo_start_xmit,即vlan_dev_hard_start_xmit,而之后skb->dev被设置成真实物理设备,所以再次进入dev_queue_xmit就会调用正常物理设备的.ndo_start_xmit将数据包发送出。
在vlan_skb_recv中有这一行代码:
skb->dev = __find_vlan_dev(dev, vlan_id);
再说这个函数是怎么找到对应的vlan设备之前,先说下vlan设备的组织方式。
数据结构vlan_group_hash是vlan虚拟网卡存储与关联的核心结构:
static struct hlist_head vlan_group_hash[VLAN_GRP_HASH_SIZE];//[net\8021q\vlan.c]
当通过vconfig创建了eth1.1, eth1.2, eth1.100三个虚拟网卡后,vlan_group_hash的整体结构如图所示,先有个整体印象:
vlan_group_hash是大小为32的hash表,所用的hash函数是:
static inline unsigned int vlan_grp_hashfn(unsigned int idx)
{
return ((idx >> VLAN_GRP_HASH_SHIFT) ^ idx) & VLAN_GRP_HASH_MASK;
}
而传入参数idx就是dev->ifindex,比如eth1的就是1。因此可以这样理解,vlan_group_hash表插入的是真实网卡设备信息(eth1)。对于一般主机来说,网卡不会太多,32个表项的hash表是完全足够的。
在添加vlan时,会创建新的vlan虚拟网卡:
register_vlan_device() -> register_vlan_dev()
首先查找网卡是否已存在,这里的real_dev一般是真实的网卡如eth1等。以real_dev->ifindex值作hash,取出vlan_group_hash的表项,由于可能存在多个网卡的hash值相同,因此还要匹配表项的real_dev是否与real_dev相同。
grp = __vlan_find_group(real_dev);
如果不存在相应的表项,则分配表项struct vlan_group,并加入vlan_group_hash:
ngrp = grp = vlan_group_alloc(real_dev);
结构定义如下,它可以代表在vlan下真实网卡的信息。real_dev指向真实网卡如eth1;nr_vlans表示网卡下创建的vlan数;vlan_devices_arrays用于存储创建的vlan虚拟网卡:
struct vlan_group {
struct net_device *real_dev;
unsigned int nr_vlans;
int killall;
struct hlist_node hlist; /* linked list */
struct net_device **vlan_devices_arrays[VLAN_GROUP_ARRAY_SPLIT_PARTS];
struct rcu_head rcu;
};
创建完表项vlan_group,紧接初始化vlan_devices_arrays二维数组中相应元素
err = vlan_group_prealloc_vid(grp, vlan_id);
最后,设置vlan_devices_arrays相应元素指向创建的vlan虚拟网卡(如eth1.1)的struct net_device。这里值得注意的是vlan_devices_arrays是二维数组(实际是一维数组,但每个元素是二级指针),内核支持的最大vlan数是4096,为了查找效率,应用了二级目录的概念。vlan_devices_arrays指向大小512的数组,数组中每个再指向大小8的数组,像eth1.100则位于第12组的第5个(vlan_devices_arrays[11][4])。
vlan_group_set_device(grp, vlan_id, dev);
以一个例子来说明,当主机收到报文,交由vlan协议模块处理后(vlan_rcv),此时需要更换skb->dev所指向的设备,以使上层协议认为报文是来自于虚拟网卡(比如eth1.1),而不知道网卡eth1的存在。更换设备就需要知道skb->dev更换的目标。这由两个因素决定:skb->dev和vlan_id。skb->dev即报文来自主机的哪个网卡,如来自eth1,则skb->dev->name=”eth1”;vlan_id即vlan号,这在报文中的vlan报文中可以提取出。有了这两个信息,从vlan_group_hash出发,首先根据skb->dev->ifindex查找vlan_group_hash的相应项(eth1),取出vlan_group;然后,根据vlan_id,在vlan_devices_array中查找到虚拟网卡设备(eth1.1)。
一般支持的最大vlan数是4096,为了查询效率,vlan_devices_array并不是一个4096的数组,而是二维数组,将每8个vlan分为一组,共512组,像eth1.100则位于第12组的第5个。
有了上面的背景,在看__find_vlan_dev就容易多了:
l __find_vlan_dev
struct net_device *__find_vlan_dev(struct net_device *real_dev, u16 vlan_id)
{
struct vlan_group *grp = __vlan_find_group(real_dev);//根据真实设备的ifindex找到对应的vlan_group
if (grp)
return vlan_group_get_device(grp, vlan_id);//再vlan_group中根据vlan_id找到对应的vlan设备
return NULL;
}
对于支持vlan(802.1q)的网卡设备,其实就相当于将vlan_skb_recv函数所做的工作下放到了网卡驱动。当网卡收到报文,提取其mac帧的13、14字节的协议号,发现是vlan协议,就会进行:
1. 从skb->date中提取VLAN id,赋值给skb->vlan_tci;
2. 除去skb->date中4字节的VLAN tag;
3. 将根据vlan_tci(即vlan id)skb->dev设置成相应虚拟vlan设备。
所以这种情况下当数据包第一次由驱动进入netif_receive_skb时,skb的dev已经被设置为了虚拟vlan设备。下面看netif_receive_skb的处理逻辑。
int netif_receive_skb(struct sk_buff *skb)
{
//……
if (skb->vlan_tci && vlan_hwaccel_do_receive(skb))
return NET_RX_SUCCESS;
//…..
}
由于skb->vlan_tci被设置为了vlan id,不为0,所以进入vlan_hwaccel_do_receive逻辑。
l vlan_hwaccel_do_receive
int vlan_hwaccel_do_receive(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct net_device_stats *stats;
//将skb->dev设置为vlan设备对应的真实设备
skb->dev = vlan_dev_info(dev)->real_dev;
netif_nit_deliver(skb);
//将skb->dev设置会对应的vlan虚拟设备
skb->dev = dev;
skb->priority = vlan_get_ingress_priority(dev, skb->vlan_tci);
skb->vlan_tci = 0;//这里保证即使后面再次进入netif_receive_skb处理逻辑,也不会进入到vlan处理逻辑。
stats = &dev->stats; //更新vlan设备的统计计数
stats->rx_packets++;
stats->rx_bytes += skb->len;
switch (skb->pkt_type) {
case PACKET_BROADCAST:
break;
case PACKET_MULTICAST:
stats->multicast++;
break;
case PACKET_OTHERHOST:
if (!compare_ether_addr(eth_hdr(skb)->h_dest,
dev->dev_addr))
skb->pkt_type = PACKET_HOST;
break;
};
return 0; //注意返回值为0,netif_receive_skb的逻辑会继续执行
}
l netif_nit_deliver
void netif_nit_deliver(struct sk_buff *skb)
{
struct packet_type *ptype;
if (list_empty(&ptype_all))
return;
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
skb->mac_len = skb->network_header - skb->mac_header;
rcu_read_lock();
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev)
deliver_skb(skb, ptype, skb->dev);
}
rcu_read_unlock();
}
可以看到netif_nit_deliver会遍历ptype_all链表,将skb发送给每个ptype_all协议,这里注意此时skb->dev被替换为真实的dev,所以无论网卡是否支持vlan,如果你在eth0设备上创建了vlan设备eth0.100,那么tcpdump再eth0上都可以抓到vlan的数据包,并不是只能再eth0.100抓包。
接下来的接收逻辑就和普通数据包一样进入netif_receive_skb。
说完接收再看下支持VLAN设备的发送逻辑。有前面知道,当物理设备支持NETIF_F_HW_VLAN_TX时:
dev->netdev_ops = &vlan_netdev_accel_ops;// acceleration加速
static const struct net_device_ops vlan_netdev_accel_ops = {
//……
.ndo_start_xmit = vlan_dev_hwaccel_hard_start_xmit,
//……
}
所以会调用vlan_dev_hwaccel_hard_start_xmit函数。
l vlan_dev_hwaccel_hard_start_xmit
static netdev_tx_t vlan_dev_hwaccel_hard_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
int i = skb_get_queue_mapping(skb);
struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
u16 vlan_tci;
unsigned int len;
int ret;
vlan_tci = vlan_dev_info(dev)->vlan_id;
vlan_tci |= vlan_dev_get_egress_qos_mask(dev, skb);
skb = __vlan_hwaccel_put_tag(skb, vlan_tci);//设置vlan id
//这里是重点,将skb->dev由虚拟的vlan设备设置为对应的真实设备
skb->dev = vlan_dev_info(dev)->real_dev;
len = skb->len;
ret = dev_queue_xmit(skb);
if (likely(ret == NET_XMIT_SUCCESS)) {
txq->tx_packets++;
txq->tx_bytes += len;
} else
txq->tx_dropped++;
return NETDEV_TX_OK;
}
对比vlan_dev_hwaccel_hard_start_xmit和不支持vlan特性的发送函数vlan_dev_hard_start_xmit,好像逻辑没什么不同啊,都是添加vlan id,修改skb->dev啊。那为什么要两套函数呢?其实是不一样的,我们看下这里是如何设置vlan id的。
l __vlan_hwaccel_put_tag
static inline struct sk_buff *__vlan_hwaccel_put_tag(struct sk_buff *skb,
u16 vlan_tci)
{
skb->vlan_tci = vlan_tci;
return skb;
}
这里可以看到,设置vlan id仅仅是设置类skb->vlan_tci,而并没有修改skb->date,从而插入4字节的VLAN tag。这个动作是交给网卡驱动做的。这就是和不支持VLAN特性设备的最大区别,不设置skb->date的VLAN tag就不需要进行字节拷贝。从而减少了cpu处理时间。所以支持VLAN特性的设备在从驱动接收到vlan mac帧时VLAN tag已经被去除,而发送时也不会添加VLAN tag,而交由驱动去添加。
其实对应vlan的接收处理,linux2.x和3.x实现还是有区别的,这里只是讲的2.x。
在linx 2.6的内核里,是通过将dev_add_pack将该接收函数注册到三层协议相关的接收函数的链表里的。即把vlan的接收函数与ip 、ipv6等协议的接收函数注册到同一个链表里的。
但是考虑到vlan毕竟是属于二层协议的范畴,因此在linux3.x中,对剥除vlan tag的操作进行了调整,即在netif_receive_skb中,即调用vlan_untag操作,剥除数据包的vlan tag,接着调用vlan_do_receive修改skb->dev的值,接着重新返回到vlan_untag的起始调用处,即实现了从real_dev->vlan_dev的转换。这样既将vlan的剥除与三层协议相关的接收函数区别开来,又省去了netif_rx的调用。