VPP以太网VLAN子接口

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

VLAN子接口

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

//create sub-interfaces <interface> <subId>

vpp# create sub-interfaces host-vpp1out 1000
host-vpp1out.1000

等价于命令:
//create sub-interfaces <interface> <subId> dot1q|dot1ad <vlanId>|any [exact-match]

vpp# create sub-interfaces host-vpp1out 1001 dot1q 1001 exact-match

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

//create sub-interfaces <interface> <subId> default

vpp# create sub-interfaces host-vpp1out 1002 default
host-vpp1out.1002

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

vpp# create sub-interfaces host-vpp1out 1003 dot1ad 1003
host-vpp1out.1003

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

外层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

untagged子接口

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

//create sub-interfaces <interface> <subId> untagged

vpp# create sub-interfaces port8 20 untagged
create sub-interfaces: vlan is already in use

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

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;

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

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

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

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;

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

接口配置

如下函数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 = &ethernet_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;

首先,对于非以太网类接口,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
  }

在获取子接口结构之前,先取得主接口结构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);

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

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

对于默认子接口,主结构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;
  }

没有任何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 {

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

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

双层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;

对于双层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;

接口添加删除

注册接口添加/删除回调函数,在添加接口时,为子接口结构设置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);

注册接口的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);

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

首先,根据物理接口索引找到主接口结构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 = &ethernet_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;

如果外层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);
      }

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

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

计算以太网头部长度(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;

根据二层接口标识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;
      }
    }

移动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);

子接口查找

按照优先级,首先比较双层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;

报文处理

如果物理接口为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;
  }

否则,如果物理接口为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);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值