• VPP以太网VLAN子接口


    子接口类型如下三类:VLAN子接口、QinQ子接口,untagged子接口。

    VLAN子接口

    创建VLAN子接口,子接口ID和VLAN ID相同,都为1000。

    //create sub-interfaces  
    
    vpp# create sub-interfaces host-vpp1out 1000
    host-vpp1out.1000
    
    等价于命令:
    //create sub-interfaces   dot1q|dot1ad |any [exact-match]
    
    vpp# create sub-interfaces host-vpp1out 1001 dot1q 1001 exact-match
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    以下创建默认的VLAN子接口,指定了default命令字,表明报文携带的VLAN ID不匹配其它任何的子接口时,发送至此默认子接口。

    //create sub-interfaces   default
    
    vpp# create sub-interfaces host-vpp1out 1002 default
    host-vpp1out.1002
    
    • 1
    • 2
    • 3
    • 4

    创建dot1ad子接口,接口ID和VLAN ID都是1003:

    vpp# create sub-interfaces host-vpp1out 1003 dot1ad 1003
    host-vpp1out.1003
    
    • 1
    • 2

    QinQ子接口

    创建q-in-q子接口,外层VLAN为1005,内层VLAN为1006。内层VLAN也可设置为任意值。

    vpp# create sub-interfaces host-vpp1out 1005 dot1q 1005 inner-dot1q 1006
    host-vpp1out.1005
    vpp#
    vpp#
    vpp# create sub-interfaces host-vpp1out 1007 dot1q 10007 inner-dot1q any
    host-vpp1out.1007
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    外层VLAN为dot1ad协议,ID值为1008;内层VLAN为dot1q协议,ID值为1009。内层VLAN可设置为任意值ANY。

    vpp# create sub-interfaces host-vpp1out 1008 dot1ad 1008 inner-dot1q 1009
    host-vpp1out.1008
    vpp#
    vpp#
    vpp# create sub-interfaces host-vpp1out 1010 dot1ad 1010 inner-dot1q any
    host-vpp1out.1010
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    untagged子接口

    创建untagged VLAN子接口,没有携带任何VLAN ID的报文发送至此子接口。注意这条命令是执行不成功的。将此功能赋予了父接口port8,没有携带VLAN ID的报文发送至port8。

    //create sub-interfaces   untagged
    
    vpp# create sub-interfaces port8 20 untagged
    create sub-interfaces: vlan is already in use
    
    • 1
    • 2
    • 3
    • 4

    在ethernet-input节点中,其维护一个物理接口所对应的主接口列表,物理接口自身对应的为untagged_subint,在物理接口注册时,以下函数将此子接口设置有效标志SUBINT_CONFIG_VALID。所以后续创建untagged类型的子接口将会报错(vlan is already in use)。

    static clib_error_t *
    ethernet_sw_interface_add_del (vnet_main_t * vnm, u32 sw_if_index, u32 is_create)
    {
      subint_config_t *subint;
      subint = ethernet_sw_interface_get_config (vnm, sw_if_index, &match_flags, &unsupported);
    
      if (subint->flags & SUBINT_CONFIG_VALID) {
          error = clib_error_return (0, "vlan is already in use");
      } else {
          subint->flags = SUBINT_CONFIG_VALID | match_flags;
          subint->sw_if_index = ~0; // because interfaces are initially down
      }
    }
    
    VNET_SW_INTERFACE_ADD_DEL_FUNCTION (ethernet_sw_interface_add_del);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ethernet-input节点

    ethernet-input节点中维护的接口结构如下main_intf_t。参见上节函数ethernet_sw_interface_add_del,在接口添加/删除时,对main_intf_t列表执行相应的操作。

    typedef struct
    {
      subint_config_t untagged_subint;
      subint_config_t default_subint;
      u16 dot1q_vlans;      // pool id for vlan table
      u16 dot1ad_vlans;     // pool id for vlan table
    } main_intf_t;
    
    typedef struct ethernet_main_t_
    {
      // The root of the vlan parsing tables. A vector with one element
      // for each main interface, indexed by hw_if_index.
      main_intf_t *main_intfs;
    
    } ethernet_main_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    报文处理路径中使用的子接口配置结构。

    // Structs used when parsing packet to find sw_if_index
    typedef struct
    {
      u32 sw_if_index;
      u32 flags;
      // config entry is-valid flag
      // exact match flags (valid if packet has 0/1/2/3 tags)
      // L2 vs L3 forwarding mode
    #define SUBINT_CONFIG_MATCH_0_TAG (1<<0)
    #define SUBINT_CONFIG_MATCH_1_TAG (1<<1)
    #define SUBINT_CONFIG_MATCH_2_TAG (1<<2)
    #define SUBINT_CONFIG_MATCH_3_TAG (1<<3)
    #define SUBINT_CONFIG_VALID       (1<<4)
    #define SUBINT_CONFIG_L2          (1<<5)
    #define SUBINT_CONFIG_P2P         (1<<6)
    
    } subint_config_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    控制平面使用的子接口结构。

    typedef struct
    { 
      /* Subinterface ID. A number 0-N to uniquely identify
       * this subinterface under the main (parent?) interface
       */ 
      u32 id;
      
      /* Classification data. Used to associate packet header with subinterface. */
      struct
      { 
        u16 outer_vlan_id;
        u16 inner_vlan_id;
        union
        { 
          u16 raw_flags;
          struct
          { 
        u16 no_tags:1;
        u16 one_tag:1;
        u16 two_tags:1;     
        u16 dot1ad:1;       /* 0 = dot1q, 1=dot1ad */
        u16 exact_match:1;
        u16 default_sub:1;
        u16 outer_vlan_id_any:1;
        u16 inner_vlan_id_any:1;
          } flags;
        };
      } eth;
    } vnet_sub_interface_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    ethernet-input节点初始化

    在ethernet-input节点初始化时,预先分配10个main_intf_t结构。

    void
    ethernet_input_init (vlib_main_t * vm, ethernet_main_t * em)
    {
      __attribute__ ((unused)) vlan_table_t *invalid_vlan_table;
      __attribute__ ((unused)) qinq_table_t *invalid_qinq_table;
    
      // Initialize pools and vector for vlan parsing
      vec_validate (em->main_intfs, 10);    // 10 main interfaces
      pool_alloc (em->vlan_pool, 10);
      pool_alloc (em->qinq_pool, 1);
    
      // The first vlan pool will always be reserved for an invalid table
      pool_get (em->vlan_pool, invalid_vlan_table); // first id = 0
      // The first qinq pool will always be reserved for an invalid table
      pool_get (em->qinq_pool, invalid_qinq_table); // first id = 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    接口配置

    如下函数ethernet_sw_interface_get_config获取(/分配)所指定接口的子接口配置,其保存在子接口结构subint_config_t中,做为返回值。如果接口不属于ethernet_hw_interface_class以太网接口类型,返回值为空。

    参数flags返回子接口类型,vlan标签的个数;参数unsupported返回失败时候的原因,包括不支持或者非以太网接口。此函数涉及的子接口类型有:P2P,PIPE,802.1q和802.1ad。

    在接口创建和删除的回调函数ethernet_sw_interface_add_del中,调用此函数。

    static subint_config_t *
    ethernet_sw_interface_get_config (vnet_main_t * vnm, u32 sw_if_index, u32 * flags, u32 * unsupported)
    {
      ethernet_main_t *em = ðernet_main;
      vnet_hw_interface_t *hi;
      vnet_sw_interface_t *si;
      main_intf_t *main_intf;
      vlan_table_t *vlan_table;
      qinq_table_t *qinq_table;
      subint_config_t *subint = 0;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    首先,对于非以太网类接口,unsupported设置为0,返回空,结束处理。

      hi = vnet_get_sup_hw_interface (vnm, sw_if_index);
    
      if (!hi || (hi->hw_class_index != ethernet_hw_interface_class.index)) {
          *unsupported = 0;
          goto done;        // non-ethernet interface
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在获取子接口结构之前,先取得主接口结构main_intf_t,如果不存在,vec_validate函数进行分配。

      // ensure there's an entry for the main intf (shouldn't really be necessary)
      vec_validate (em->main_intfs, hi->hw_if_index);
      main_intf = vec_elt_at_index (em->main_intfs, hi->hw_if_index);
    
      // Locate the subint for the given ethernet config
      si = vnet_get_sw_interface (vnm, sw_if_index);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    对于P2P子接口类型,如果没有子接口subint_config_t结构,由p2p_main模块的p2p_subif_pool池中分配。参数flags设置为SUBINT_CONFIG_P2P。

      if (si->type == VNET_SW_INTERFACE_TYPE_P2P) {
          p2p_ethernet_main_t *p2pm = &p2p_main;
          u32 p2pe_sw_if_index = p2p_ethernet_lookup (hi->hw_if_index, si->p2p.client_mac);
          if (p2pe_sw_if_index == ~0) {
            pool_get (p2pm->p2p_subif_pool, subint);
            si->p2p.pool_index = subint - p2pm->p2p_subif_pool;
          }
          else
            subint = vec_elt_at_index (p2pm->p2p_subif_pool, si->p2p.pool_index);
          *flags = SUBINT_CONFIG_P2P;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    对于PIPE子接口类型,pipe_main模块中取得成员subint,即为要找的subint_config_t结构。参数flags设置为SUBINT_CONFIG_P2P。

      else if (si->type == VNET_SW_INTERFACE_TYPE_PIPE) {
          pipe_t *pipe;
    
          pipe = pipe_get (sw_if_index);
          subint = &pipe->subint;
          *flags = SUBINT_CONFIG_P2P;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对于默认子接口,主结构main_intf中成员default_subint,即为subint_config_t结构。flags表明接口可匹配3层VLAN标签,没有找到匹配子接口的VLAN报文,发送到此默认接口。flags设置了三个TAG标志,都可匹配。

      else if (si->sub.eth.flags.default_sub) {
          subint = &main_intf->default_subint;
          *flags = SUBINT_CONFIG_MATCH_1_TAG | SUBINT_CONFIG_MATCH_2_TAG | SUBINT_CONFIG_MATCH_3_TAG;
      }
    
    • 1
    • 2
    • 3
    • 4

    没有任何tag标签,说明为主接口,返回主结构main_intf中成员untagged_subint,其为subint_config_t结构。flags表明不匹配任何标签。

      else if ((si->sub.eth.flags.no_tags) || (si->sub.eth.raw_flags == 0)) {
          // if no flags are set then this is a main interface so treat as untagged
          subint = &main_intf->untagged_subint;
          *flags = SUBINT_CONFIG_MATCH_0_TAG;
      } else {
    
    • 1
    • 2
    • 3
    • 4
    • 5

    VLAN子接口结构

    一个或者两个tag的情况。对于dot1ad协议或者dot1q协议,如果主接口结构main_intf中成员dot1ad_vlans/dot1q_vlans等于0,表明vlan表还未分配,由ethernet_main的成员vlan_pool中进行分配。

    否则,根据dot1ad_vlans/dot1q_vlans的值,在vlan_pool中获得vlan_table结构。

          // one or two tags. first get the vlan table
        if (si->sub.eth.flags.dot1ad) {
          if (main_intf->dot1ad_vlans == 0) {
              pool_get (em->vlan_pool, vlan_table);
              main_intf->dot1ad_vlans = vlan_table - em->vlan_pool;
          } else
              vlan_table = vec_elt_at_index (em->vlan_pool, main_intf->dot1ad_vlans);
        } else {           // dot1q
          if (main_intf->dot1q_vlans == 0) {
              pool_get (em->vlan_pool, vlan_table);
              main_intf->dot1q_vlans = vlan_table - em->vlan_pool;
          } else
              vlan_table = vec_elt_at_index (em->vlan_pool, main_intf->dot1q_vlans);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    单层vlan的情况,需要指定vlan id,暂不支持指定任意vlan。由vlan表中,根据vlan id取出对应的subint_config_t结构。如果指定了精确匹配exact_match,仅匹配一层VLAN标签;否则,匹配三层标签。

        if (si->sub.eth.flags.one_tag) {
          *flags = si->sub.eth.flags.exact_match ? SUBINT_CONFIG_MATCH_1_TAG :
            (SUBINT_CONFIG_MATCH_1_TAG | SUBINT_CONFIG_MATCH_2_TAG | SUBINT_CONFIG_MATCH_3_TAG);
    
          if (si->sub.eth.flags.outer_vlan_id_any) {
              *unsupported = 1;
              goto done;
          } else {
              // a single vlan, a common case
              subint = &vlan_table->vlans[si->sub.eth.outer_vlan_id].single_tag_subint;
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    双层vlan的情况,不支持双层VLAN都设置为any的情况。允许内层vlan设置为any,此时根据外层vlan id在vlan_table表中找到子接口结构inner_any_subint。如果指定了exact_match,需匹配二层VLAN标签;否则,可匹配二层或者三层VLAN标签。

        } else { // Two tags
          *flags = si->sub.eth.flags.exact_match ? SUBINT_CONFIG_MATCH_2_TAG :
                   (SUBINT_CONFIG_MATCH_2_TAG | SUBINT_CONFIG_MATCH_3_TAG);
    
          if (si->sub.eth.flags.outer_vlan_id_any && si->sub.eth.flags.inner_vlan_id_any) {
              *unsupported = 1;   // not implemented yet
              goto done;
          }
          if (si->sub.eth.flags.inner_vlan_id_any) {
              // a specific outer and "any" inner. don't need a qinq table for this
              subint = &vlan_table->vlans[si->sub.eth.outer_vlan_id].inner_any_subint;
            if (si->sub.eth.flags.exact_match)
              *flags = SUBINT_CONFIG_MATCH_2_TAG;
            else
              *flags = SUBINT_CONFIG_MATCH_2_TAG | SUBINT_CONFIG_MATCH_3_TAG;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    对于双层vlan都有确定的VLAN ID的情况,需要使用qinq_table表。首先,根据外层vlan id值在vlan_table中取得qinq表的索引,如果不存在,由qinq_pool池中新分配。子接口结构使用内层vlan id为索引,数组qinq_table->vlans的成员subint。

          } else {           // a specific outer + specific innner vlan id, a common case
            if (vlan_table->vlans[si->sub.eth.outer_vlan_id].qinqs == 0) {
              pool_get (em->qinq_pool, qinq_table);
              vlan_table->vlans[si->sub.eth.outer_vlan_id].qinqs = qinq_table - em->qinq_pool;
            } else {
              qinq_table = vec_elt_at_index (em->qinq_pool,
                          vlan_table->vlans[si->sub.eth.outer_vlan_id].qinqs);
            }
            subint = &qinq_table->vlans[si->sub.eth.inner_vlan_id].subint;
          }
        }
        }
    done:
      return subint;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    接口添加删除

    注册接口添加/删除回调函数,在添加接口时,为子接口结构设置flags相关标志;在删除接口时,清除flags标志。

    static clib_error_t *
    ethernet_sw_interface_add_del (vnet_main_t * vnm, u32 sw_if_index, u32 is_create)
    {
      subint_config_t *subint;
    
      subint = ethernet_sw_interface_get_config (vnm, sw_if_index, &match_flags, &unsupported);
      if (subint == 0) { 
          if (unsupported) error = clib_error_return (0, "not implemented yet");
          goto done;
      }
      if (!is_create) {
          subint->flags = 0;
          return error;
      }
      if (subint->flags & SUBINT_CONFIG_VALID)
          error = clib_error_return (0, "vlan is already in use");
      else {
          subint->flags = SUBINT_CONFIG_VALID | match_flags;
          subint->sw_if_index = ~0; // because interfaces are initially down
      }
    done:
      return error;
    }
    VNET_SW_INTERFACE_ADD_DEL_FUNCTION (ethernet_sw_interface_add_del);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    注册接口的UP/DOWN回调函数,在接口UP/DOWN时,设置子接口的索引值sw_if_index。这样,在处理接收到的报文时,flags匹配,可定位到子接口索引。

    static clib_error_t *
    ethernet_sw_interface_up_down (vnet_main_t * vnm, u32 sw_if_index, u32 flags)
    { 
      subint_config_t *subint;
      
      subint = ethernet_sw_interface_get_config (vnm, sw_if_index, &placeholder_flags, &placeholder_unsup);
      if (subint == 0) goto done;
      
      subint->sw_if_index = ((flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) ? sw_if_index : ~0);
    
    done:
      return error;
    }
    
    VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (ethernet_sw_interface_up_down);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    VLAN查找

    报文在进入ethernet-input节点之后,由函数eth_input_process_frame处理。在慢速处理路径中,对于外层协议为ETHERNET_TYPE_VLAN或者DOT1AD,使用不同的参数调用函数eth_input_tag_lookup进行处理。

    eth_input_process_frame (vlib_main_t * vm, vlib_node_runtime_t * node,
                 vnet_hw_interface_t * hi, u32 * buffer_indices, u32 n_packets, int main_is_l3, )
    {
      u16 et_vlan = clib_host_to_net_u16 (ETHERNET_TYPE_VLAN);
      u16 et_dot1ad = clib_host_to_net_u16 (ETHERNET_TYPE_DOT1AD);
      n_left = n_slowpath;
      u16 *si = slowpath_indices;
      eth_input_tag_lookup_t dot1ad_lookup, dot1q_lookup = {
          .mask = -1LL,
          .tag = tags[si[0]] ^ -1LL,
          .sw_if_index = ~0
      };
      clib_memcpy_fast (&dot1ad_lookup, &dot1q_lookup, sizeof (dot1q_lookup));
      
      while (n_left) {
        i = si[0];
        u16 etype = etypes[i];
       
        if (etype == et_vlan) {
            vlib_buffer_t *b = vlib_get_buffer (vm, buffer_indices[i]);
            eth_input_tag_lookup (vm, vnm, node, hi, tags[i], nexts + i, b,
                      &dot1q_lookup, dmacs_bad[i], 0, main_is_l3, dmac_check);
        }
        else if (etype == et_dot1ad) {
            vlib_buffer_t *b = vlib_get_buffer (vm, buffer_indices[i]);
            eth_input_tag_lookup (vm, vnm, node, hi, tags[i], nexts + i, b,
                      &dot1ad_lookup, dmacs_bad[i], 1, main_is_l3, dmac_check);
        }
        else
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    首先,根据物理接口索引找到主接口结构main_intf_t,由于tag变量长度为8字节,包含了报文的两个VLAN头部,取出外层和内层VLAN ID值(vlan1和vlan2)。如果外层协议为dot1ad,根据主结构成员dot1ad_vlans中保存的索引,取得VLAN表;否则,根据dot1q_vlans中保存的索引,取得vlan_table表。

    其次,根据外层VLAN值在vlan表中取得vlan接口信息,再根据vlan接口结构成员qinqs索引,在qinq_pool中取得qinq_table表,再由内层VLAN值(vlan2)在qinq表中取得qif接口结构。

    如果接下来的报文携带的VLAN字段与之前的报文相同,不再进行接口查找,直接使用之前保存的接口索引值。

    eth_input_tag_lookup (vlib_main_t * vm, vnet_main_t * vnm, ...)
    {
      ethernet_main_t *em = ðernet_main;
    
      if ((tag ^ l->tag) & l->mask)
        {
          main_intf_t *mif = vec_elt_at_index (em->main_intfs, hi->hw_if_index);
          vlan_intf_t *vif;
          qinq_intf_t *qif;
          vlan_table_t *vlan_table;
          qinq_table_t *qinq_table;
          u16 *t = (u16 *) & tag;
          u16 vlan1 = clib_net_to_host_u16 (t[0]) & 0xFFF;
          u16 vlan2 = clib_net_to_host_u16 (t[2]) & 0xFFF;
          u32 matched, is_l2, new_sw_if_index;
    
          vlan_table = vec_elt_at_index (em->vlan_pool, is_dot1ad ? mif->dot1ad_vlans : mif->dot1q_vlans);
          vif = &vlan_table->vlans[vlan1];
          qinq_table = vec_elt_at_index (em->qinq_pool, vif->qinqs);
          qif = &qinq_table->vlans[vlan2];
          l->err = ETHERNET_ERROR_NONE;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    如果外层VLAN携带的类型值为ETHERNET_TYPE_VLAN,表明报文携带了双层VLAN。否则,报文中仅有一层VLAN,如果其值为0,接口索引保持原值。否则,由函数eth_identify_subint来确定VLAN子接口索引new_sw_if_index和二层接口标识is_l2。

          l->type = clib_net_to_host_u16 (t[1]);
          if (l->type == ETHERNET_TYPE_VLAN)  {
            l->type = clib_net_to_host_u16 (t[3]);
            l->n_tags = 2;
            matched = eth_identify_subint (hi, SUBINT_CONFIG_VALID |
                         SUBINT_CONFIG_MATCH_2_TAG, mif, vif, qif, &new_sw_if_index, &l->err, &is_l2);
          } else {
            l->n_tags = 1;
            if (vlan1 == 0) {
              new_sw_if_index = hi->sw_if_index;
              l->err = ETHERNET_ERROR_NONE;
              matched = 1;
              is_l2 = main_is_l3 == 0;
            } else
              matched = eth_identify_subint (hi, SUBINT_CONFIG_VALID |
                           SUBINT_CONFIG_MATCH_1_TAG, mif, vif, qif, &new_sw_if_index, &l->err, &is_l2);
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    如果新的接口索引与之前接口索引不相同,首先将报文和字节统计值更新到之前的接口统计中;随后,清空统计值,更新接口索引,之后统计此新接口的报文和字节。

          if (l->sw_if_index != new_sw_if_index) {
            eth_input_update_if_counters (vm, vnm, l);
            l->n_packets = 0;
            l->n_bytes = 0;
            l->sw_if_index = new_sw_if_index;
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    计算以太网头部长度(ethernet_header_t),以及VLAN头部长度(ethernet_vlan_header_t),将两者之和记录在len中,表示二层头部的长度。如果主接口为L3三层接口,子接口为L2层接口,adv的值跳回到以太网头部开始处;否则,子接口为L3层接口的情况,adv向后跳过所有的VLAN头部。

    如果主接口为L2二层接口,子接口也为L2层接口,adv没有变化;否则,子接口为L3层接口,adv长度跳过以太网头部和所有VLAN头部。

          l->tag = tag;
          l->mask = (l->n_tags == 2) ? clib_net_to_host_u64 (0xffffffffffffffff) :
                                       clib_net_to_host_u64 (0xffffffff00000000);
    
          if (matched && l->sw_if_index == ~0)
            l->err = ETHERNET_ERROR_DOWN;
    
          l->len = sizeof (ethernet_header_t) + l->n_tags * sizeof (ethernet_vlan_header_t);
          if (main_is_l3)
            l->adv = is_l2 ? -(int) sizeof (ethernet_header_t) :
                     l->n_tags * sizeof (ethernet_vlan_header_t);
          else
            l->adv = is_l2 ? 0 : l->len;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    根据二层接口标识is_l2,以及报文类型,来确定下一个执行节点node。

          if (PREDICT_FALSE (l->err != ETHERNET_ERROR_NONE))
            l->next = ETHERNET_INPUT_NEXT_DROP;
          else if (is_l2)
            l->next = em->l2_next;
          else if (l->type == ETHERNET_TYPE_IP4)
            l->next = em->l3_next.input_next_ip4;
          else if (l->type == ETHERNET_TYPE_IP6)
            l->next = em->l3_next.input_next_ip6;
          else if (l->type == ETHERNET_TYPE_MPLS)
            l->next = em->l3_next.input_next_mpls;
          else if (em->redirect_l3)
            l->next = em->redirect_l3_next;
          else {
            l->next = eth_input_next_by_type (l->type);
            if (l->next == ETHERNET_INPUT_NEXT_PUNT)
              l->err = ETHERNET_ERROR_UNKNOWN_TYPE;
          }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    移动vlib_buffer_t指针指向三层头部(移动l->adv字节),设置vnet_buffer (b)->l2.l2_len和vnet_buffer (b)->l3_hdr_offset的值。更新sw_if_index[VLIB_RX]为子接口索引值。

      if (check_dmac && l->adv > 0 && dmac_bad) {
          l->err = ETHERNET_ERROR_L3_MAC_MISMATCH;
          next[0] = ETHERNET_INPUT_NEXT_PUNT;
      } else
        next[0] = l->next;
    
      vlib_buffer_advance (b, l->adv);
      vnet_buffer (b)->l2.l2_len = l->len;
      vnet_buffer (b)->l3_hdr_offset = vnet_buffer (b)->l2_hdr_offset + l->len;
    
      if (l->err == ETHERNET_ERROR_NONE) {
          vnet_buffer (b)->sw_if_index[VLIB_RX] = l->sw_if_index;
          ethernet_buffer_set_vlan_count (b, l->n_tags);
      } else
        b->error = node->errors[l->err];
    
      /* update counters */
      l->n_packets += 1;
      l->n_bytes += vlib_buffer_length_in_chain (vm, b);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    子接口查找

    按照优先级,首先比较双层VLAN的情况,qinq子接口,以及inner_any_subint接口(内层VLAN任意)。其次,比较单层VLAN的情况,single_tag_subint。最后,是默认子接口default_subint,和无标签接口untagged_subint。以上都没有匹配,返回匹配失败(返回值0)。

    发生匹配时,由参数new_sw_if_index和is_l2返回新的子接口索引和是否为二层接口的标志。

    eth_identify_subint (vnet_hw_interface_t * hi, u32 match_flags,
        main_intf_t * main_intf, vlan_intf_t * vlan_intf,
        qinq_intf_t * qinq_intf, u32 * new_sw_if_index, u8 * error0, u32 * is_l2)
    {
      subint_config_t *subint;
    
      // Each comparison is checking both the valid flag and the number of tags
      // (incorporating exact-match/non-exact-match).
    
      // check for specific double tag
      subint = &qinq_intf->subint;
      if ((subint->flags & match_flags) == match_flags)
        goto matched;
    
      // check for specific outer and 'any' inner
      subint = &vlan_intf->inner_any_subint;
      if ((subint->flags & match_flags) == match_flags)
        goto matched;
    
      // check for specific single tag
      subint = &vlan_intf->single_tag_subint;
      if ((subint->flags & match_flags) == match_flags)
        goto matched;
    
      // check for default interface
      subint = &main_intf->default_subint;
      if ((subint->flags & match_flags) == match_flags)
        goto matched;
    
      // check for untagged interface
      subint = &main_intf->untagged_subint;
      if ((subint->flags & match_flags) == match_flags)
        goto matched;
    
      // No matching subinterface
      *new_sw_if_index = ~0;
      *error0 = ETHERNET_ERROR_UNKNOWN_VLAN;
      *is_l2 = 0;
      return 0;
    
    matched:
      *new_sw_if_index = subint->sw_if_index;
      *is_l2 = subint->flags & SUBINT_CONFIG_L2;
      return 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    报文处理

    如果物理接口为L3三层模式,状态也是三层模式(STATUS_L3),跳过对报文目的MAC地址的检测,因为硬件网卡已经完成。或者物理接口存在L2二层子接口,并且没有任何L3层子接口,表明只需对报文进行转发,也不需要检测目的MAC地址。

    static_always_inline void
    eth_input_single_int (vlib_main_t * vm, vnet_hw_interface_t * hi, ...)
    {
      ethernet_interface_t *ei;
      ei = pool_elt_at_index (em->interfaces, hi->hw_instance);
      main_intf_t *intf0 = vec_elt_at_index (em->main_intfs, hi->hw_if_index);
      subint_config_t *subint0 = &intf0->untagged_subint;
    
      int main_is_l3 = (subint0->flags & SUBINT_CONFIG_L2) == 0;
      int int_is_l3 = ei->flags & ETHERNET_INTERFACE_FLAG_STATUS_L3;
    
      if (main_is_l3) {
        if (int_is_l3 ||      /* DMAC filter already done by NIC */
          ((hi->l2_if_count != 0) && (hi->l3_if_count == 0)))
        {           /* All L2 usage - DMAC check not needed */
          eth_input_process_frame (vm, node, hi, from, n_pkts,
                       /*is_l3 */ 1, ip4_cksum_ok, 0);
        } else
        {           /* DMAC check needed for L3 */
          eth_input_process_frame (vm, node, hi, from, n_pkts,
                       /*is_l3 */ 1, ip4_cksum_ok, 1);
        }
        return;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    否则,如果物理接口为L2模式,并且不存在L3层的子接口,也不需要检测报文的目的MAC地址。否则,对DMAC进行检查,函数eth_input_process_frame最后一个参数设置为1。

      else {
        if (hi->l3_if_count == 0)
        {           /* All L2 usage - DMAC check not needed */
          eth_input_process_frame (vm, node, hi, from, n_pkts,
                       /*is_l3 */ 0, ip4_cksum_ok, 0);
        } else
        {           /* DMAC check needed for L3 */
          eth_input_process_frame (vm, node, hi, from, n_pkts,
                       /*is_l3 */ 0, ip4_cksum_ok, 1);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    【算法】归并排序 详解
    [附源码]计算机毕业设计数字乡村基础治理系统Springboot程序
    LeetCode 每日一题 2022/9/12-2022/9/18
    Pro_07丨波动率因子3.0与斜率因子
    蓝桥杯冲刺_二分(正在补题)
    milvus Upsert api数据结构源码分析
    第六节:Word中对象的层次结构
    基于uniapp框架的古汉语学习考试系统 微信小程序python+java+node.js+php
    前端大佬看着Java程序员的头 慕了!!!
    manim边学边做--MathTex
  • 原文地址:https://blog.csdn.net/sinat_20184565/article/details/132796872