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.1QVLAN帧格式如下:

Type:长度为2字节,取值为0x8100,表示此帧的类型为802.1Q Tag帧。

PRI:长度为3比特,可取07之间的值,表示帧的优先级,值越大优先级越高。该优先级主要为QoS差分服务提供参考依据(COS)。

VLAN Identifier (VID) : 长度12bits,可配置的VLAN ID取值范围为14094。通常vlan 0vlan 4095预留,vlan1为缺省vlan,一般用于网管

注意:这里的两个Type,前面802.1Q Tag中的Type,指明这个是VLAN报文,其值为0x8100;而对于后面Length/Type中的Type指定的是以太网内层协议的类型,如IPARP等。

相关数据结构

1.1 struct vlan_ethhdr

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

点击(此处)折叠或打开

  1. struct vlan_ethhdr 
  2.    unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
  3.    unsigned char h_source[ETH_ALEN]; /* source ether addr */
  4.    __be16 h_vlan_proto; /* Should always be 0x8100 */
  5.    __be16 h_vlan_TCI; /* Encapsulates priority and VLAN ID */
  6.    __be16 h_vlan_encapsulated_proto; /* packet type ID field (or len) */
  7. ;

1.2 struct vlan_hdr

vlan头部关联的结构体

点击(此处)折叠或打开

  1. struct vlan_hdr 
  2.    __be16 h_vlan_TCI; /* Encapsulates priority and VLAN ID */
  3.    __be16 h_vlan_encapsulated_proto; /* packet type ID field (or len) */
  4. ;

不支持VLAN的网卡

    对于不支持VLAN的网卡,也就不能识别报文中Type0x8100这个类型有什么特殊之处,网卡驱动会将其当作普通mac帧收上来。注意此时,如果是正常的mac帧(非VLAN),skb->protocol会被设置成mac帧的第1314字节,也就是(Length/Type)中的Type,而对于VLANmac帧来说同样会被设置为mac帧的第1314字节,但此时是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(),由于type802.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

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()来复制一份skbkfree_skb()并不一定释放skb,只有当skb->users1时,才会释放;否则只是递减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指向了真正的数据字段,如iparp

    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协议

网络协议栈源码分析

Linux 网络协议栈开发基础篇—— 使用wireshark分析TCP/IP协议中TCP包头的格式

Linux 网络协议栈开发基础篇—— 使用wireshark分析TCP/IP协议中TCP包头的格式