zoukankan      html  css  js  c++  java
  • vlan

    声明:

    本文大部分内容来源网络,结合作者整理而成。

    http://blog.chinaunix.net/uid-28541347-id-5746202.html

    http://blog.csdn.net/qy532846454/article/details/6529166

    1. 写在前面的话

      内核代码读多了,就会发现数据结构的重要性。实际上,不管任何程序,内核层面的也好,应用层面的也罢, 亦或者图形界面程序,开发者只需要考虑好数据的存储和数据利用两大问题基本上就可以大功告成了。同样的道理,阅读内核代码的时候,重要的是读懂该模块数据组织形式,这对于后面理解作者的设计思想和其它细枝末节的东西能达到事半功倍的效果。因此,本篇文章以树结构为切入点,详细介绍vlan模块在linux 内核中的实现。

    2. 数据结构

      当通过vconfig创建了eth1.1, eth1.2, eth1.100三个虚拟网卡后,内核中vlan的整体结构如图所示,先有个整体印象:

      

      

      vlan_group_hash是大小为32的hash表,所用的hash函数是:

      

    1 static inline unsigned int vlan_grp_hashfn(unsigned int idx)  
    2 {  
    3  return ((idx >> VLAN_GRP_HASH_SHIFT) ^ idx) & VLAN_GRP_HASH_MASK;  
    4 }  

      传入参数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_group 结构定义如下,它代表在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;  
    };  

      根据hash值找到真实网卡,接下来就可以在该网卡下面增删改的vlan虚拟网卡(如eth1.1)的struct net_device。vlan虚拟网卡的信息是存放在vlan_devices_arrays中的 ,这里值得注意的是vlan_devices_arrays是二维数组(实际上vlan_devices_arrays是一维数组,它的每个成员是指向struct net_device *指针类型的一维数组,因此达到了二维数组的效果),内核支持的最大vlan数是4096,为了查找效率,应用了二级目录的概念。vlan_devices_arrays指向大小8的数组,数组中每个再指向大小512的数组。

      以一个例子来说明,当主机收到报文,交由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的数组,而是二维数组,将每512个vlan分为一组,共8组。

    2. vlan数据的传输 

    2.1 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等。

    2.2 相关数据结构

    包含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 };

    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 };

    2.3 vlan数据接收

      对于不支持VLAN的网卡,如果是正常的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函数。

      

      

     1 int netif_receive_skb(struct sk_buff *skb)
     2 {
     3 struct packet_type *ptype, *pt_prev;
     4 //这里是重点,但是只有网卡支持VLAN时才会设置skb->vlan_tci
     5     if (skb->vlan_tci && vlan_hwaccel_do_receive(skb))
     6         return NET_RX_SUCCESS;
     7     //……
     8     //遍历ptye_all链表, 上面的paket_type.type 为 ETH_P_ALL,
     9     list_for_each_entry_rcu(ptype, &ptype_all, list) {
    10         if (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
    11             ptype->dev == orig_dev) {
    12 if (pt_prev)//注意,此时orig_dev为物理dev,如eth0
    13        // 此函数最终调用paket_type.func()
    14                 ret = deliver_skb(skb, pt_prev, orig_dev);
    15             pt_prev = ptype;
    16         }
    17     }
    18     //bridge逻辑(可以看到bridge逻辑再VLAN处理之前)
    19 skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
    20     //这里和VLAN没有关系,而是mac-vlan的相关功能,编译内核时选上MAC_VLAN模块,下面才会执行
    21 skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
    22 //这里的type被置为VLAN协议,即0x8100
    23 type = skb->protocol;
    24     //处理ptype_base[ntohs(type)&15]上的所有的 packet_type->func()
    25     list_for_each_entry_rcu(ptype,
    26             &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
    27         if (ptype->type == type &&
    28             (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
    29              ptype->dev == orig_dev)) {
    30 if (pt_prev)
    31                 //此函数最终调用paket_type.func(),由于type为802.1Q的协议,所以会调用其对应的协议处理函数。
    32                 ret = deliver_skb(skb, pt_prev, orig_dev);
    33             pt_prev = ptype;
    34         }
    35 }
    36 //……
    37 }

    在加载8021q时会注册相应packet_type,同时初始化相关处理函数func。

      

    1 static struct packet_type vlan_packet_type __read_mostly = {  
    2  .type = cpu_to_be16(ETH_P_8021Q),  
    3  .func = vlan_skb_recv, /* VLAN receive method */  
    4 }; 

     所以接下来会调用vlan_skb_recv函数。

     net/8021q/vlan_dev.c

    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,内核协议栈中经常会看到次函数的调用,次函数是为了确保skb->head和skb->data之间的空间能够存放vlan头部
        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;
    }
    


    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); }

    vlan_check_reorder_header

     1 static inline struct sk_buff *vlan_check_reorder_header(struct sk_buff *skb)
     2 {
     3     if (vlan_dev_info(skb->dev)->flags & VLAN_FLAG_REORDER_HDR) {
     4         if (skb_cow(skb, skb_headroom(skb)) < 0)
     5             skb = NULL;
     6         if (skb) {
     7             //这个是重点,ETH_HLEN=14,VLAN_ETH_HLEN=18
     8             memmove(skb->data - ETH_HLEN, skb->data - VLAN_ETH_HLEN, 12);
     9             skb->mac_header += VLAN_HLEN;// VLAN_HLEN=4
    10         }
    11     }
    12     return skb;
    13 }

     函数vlan_check_reorder_header负责去掉报文中的vlan tag,要理解此函数,关键是理解执行此函数前后vlan->data指向的位置,在执行此函数之前,调用了skb_pull_rcsum(skb, VLAN_HLEN);

    unsigned char *skb_pull_rcsum(struct sk_buff *skb, unsigned int len)
    2483 {         
    2484         BUG_ON(len > skb->len);
    2485         skb->len -= len; 
    2486         BUG_ON(skb->len < skb->data_len);
    2487         skb_postpull_rcsum(skb, skb->data, len);
    2488         return skb->data += len;
    2489 }   
    2490    

    可以看到,在skb_pull_rcsum的末尾,将skb->data的指针真正的指向了l2的数据头部。

    明白了这一点,接下来我们可以继续看vlan_check_reorder_header函数,此函数的重点是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特性时的接收逻辑如下图:

    2.4 vlan数据发送

    /net/8021q/vlan_dev.c

     1 static int vlan_dev_init(struct net_device *dev)
     2 {
     3   //……
     4 /*根据real device是否支持NETIF_F_HW_VLAN_TX,让vlan device的netdev_ops指针指向不同的接口函数。*/
     5     if (real_dev->features & NETIF_F_HW_VLAN_TX) {
     6         dev->header_ops      = real_dev->header_ops;
     7         dev->hard_header_len = real_dev->hard_header_len;
     8         dev->netdev_ops         = &vlan_netdev_accel_ops;
     9     } else {
    10         dev->header_ops      = &vlan_header_ops;
    11         dev->hard_header_len = real_dev->hard_header_len + VLAN_HLEN;
    12         dev->netdev_ops         = &vlan_netdev_ops;
    13     }
    14   //……
    15 }
    16 static const struct net_device_ops vlan_netdev_ops = {
    17 //……
    18     .ndo_start_xmit =  vlan_dev_hard_start_xmit,
    19 //……
    20 }

     所以真实设备不支持vlan时,发送或调用 vlan_dev_hard_start_xmit函数。

     1 static netdev_tx_t vlan_dev_hard_start_xmit(struct sk_buff *skb,
     2                         struct net_device *dev)
     3 {
     4     int i = skb_get_queue_mapping(skb);
     5     struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
     6     struct vlan_ethhdr *veth = (struct vlan_ethhdr *)(skb->data);
     7     unsigned int len;
     8 int ret;
     9 //如果mac的协议类型不是vlan协议,说明还没有打上VLAN tag,则在此处添加上4字节的VLAN tag
    10     if (veth->h_vlan_proto != htons(ETH_P_8021Q) ||
    11         vlan_dev_info(dev)->flags & VLAN_FLAG_REORDER_HDR) {
    12         unsigned int orig_headroom = skb_headroom(skb);
    13         u16 vlan_tci;
    14         vlan_dev_info(dev)->cnt_encap_on_xmit++;
    15         vlan_tci = vlan_dev_info(dev)->vlan_id; //获取到vlan设备的vlan id
    16         vlan_tci |= vlan_dev_get_egress_qos_mask(dev, skb);
    17         skb = __vlan_put_tag(skb, vlan_tci);//在报文中添加VLAN tag
    18         if (!skb) {
    19             txq->tx_dropped++;
    20             return NETDEV_TX_OK;
    21         }
    22         if (orig_headroom < VLAN_HLEN)
    23             vlan_dev_info(dev)->cnt_inc_headroom_on_tx++;
    24 }
    25 //这里是重点,skb->dev设置为真实设备的dev
    26     skb->dev = vlan_dev_info(dev)->real_dev;
    27     len = skb->len;
    28     ret = dev_queue_xmit(skb);//再次调用dev_queue_xmit
    29     if (likely(ret == NET_XMIT_SUCCESS)) {
    30         txq->tx_packets++;
    31         txq->tx_bytes += len;
    32     } else
    33         txq->tx_dropped++;
    34     return NETDEV_TX_OK;
    35 }

      我们知道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将数据包发送出。

    3. 总结:不难发现,其实vlan和bridge背后的设计思想是一致的,即不管是虚拟设备还是真实设备,skb -> dev总是指向当前数据流通过的设备,且数据流切换一次设备(从虚拟设备到真实设备或者从真实设备到虚拟设备)就调用一次netif_receive_skb函数(或者对应的数据发送函数),这种模块化的设计思想更有助于我们理解内核中数据流向.

  • 相关阅读:
    数据库中的索引结构是什么?
    什么情况下适合建立索引?
    python requests https 访问出错
    Centos下 自动化配置SSH免密码登陆
    expect 批量增加用户及配置密码
    Shell 处理文件名中包含空格的文件
    Linux sort 命令
    ictclas bug修复
    [转]hadoop2.x常用端口
    在服务器上运行Jar包
  • 原文地址:https://www.cnblogs.com/3me-linux/p/6595834.html
Copyright © 2011-2022 走看看