前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >learning vpp:解析vlan处理流程(2)

learning vpp:解析vlan处理流程(2)

作者头像
dpdk-vpp源码解读
发布2024-04-15 15:26:54
2130
发布2024-04-15 15:26:54
举报
文章被收录于专栏:DPDK VPP源码分析DPDK VPP源码分析

具体在上一章节中《learning vpp:解析创建vlan子接口代码(1)》我们介绍了vpp创建vlan子接口命令行及配置逻辑流程的,接下来,以qinq接口为例,学习三层vlan处理流程。

首先简单了解一下什么是qinq?QinQ(802.1Q-in-802.1Q)也叫做VLAN Stacking或Double VLAN,由IEEE 802.1ad标准定义,是一项扩展VLAN空间的技术,通过在802.1Q标签报文的基础上再增加一层802.1Q的Tag来达到扩展VLAN空间的目的。一般应用在骨干网中,通过将用户私网VLAN Tag封装在公网VLAN Tag中,使报文带着两层VLAN Tag穿越运营商的骨干网络(公网),扩充VLAN数量,实现对用户的精细化管理。

QinQ封装报文是在无标签的以太网数据帧的源MAC地址字段后面加上两个VLAN标签构成。

  • TPID(Tag Protocol Identifier,标签协议标识)表示帧类型。取值为0x8100时表示802.1Q Tag帧。如果不支持802.1Q的设备收到这样的帧,会将其丢弃。
  • 对于内层的802.1Q Tag,该值设置为0x8100;对于外层的802.1Q Tag,不同厂商所使用的值可能不相同:
    • 0x8100:Huawei路由器使用(linux内核默认也是0x8100)
    • 0x88A8:802.1ad规定外层802.1Q Tag中的TPID为0x88a8
  • 在华为设备上,外层802.1Q Tag缺省情况下值为0x8100,可以通过命令行调整该值。

如下图所示,通过对802.1Q封装和QinQ封装的报文抓包,可以明显看出QinQ报文比802.1Q多了一层802.1Q标签。

QinQ技术的应用场景,本文就不介绍的,想了解的可以参考文章结尾链接。本文主要通过在vpp配置三层qinq接口来学习业务转发流程。

下面是在vpp创建tap10接口,可以实现vpp和内核之间的通信,然后分别在内核及vpp创建qinq接口,具体配置如下:

VPP创建一个外层vlan id 10 内层vlan id 100的qing子接口,并设置接口ip地址192.168.1.1/24.

代码语言:javascript
复制
#创建一个tap10接口
create tap id 10 host-if-name tap10
#创建一个外层VLan id 10 内层100的qinq子接口,并设置接口up及配置ip地址。
create sub-interfaces tap10 10 dot1q 10 inner-dot1q 100 exact-match
set interface state tap10 up
set interface state tap10.10 up
set interface ip address tap10.10 192.168.1.1/24

同样在内核创建一个外层vlan id 10 内层vlan id 100的qinq子接口tap10.10.100

代码语言:javascript
复制
ip link add link tap10 name tap10.10 type vlan id 10
ip link add link tap10.10 name tap10.10.100 type vlan id 100
ip link set tap10.10 up
ip link set tap10.10.100 up
ip addr add 192.168.1.2/24 dev tap10.10.100

接下来我们分别查询vpp创建接口tap10.10及内核tap10.10.100接口情况,并在内核ping 网关192.168.1.1可以ping 通,具体查询接口如下:

代码语言:javascript
复制
#1、在vpp查询接口信息
pdk-vpp源码分析: show interface addr
local0 (dn):
tap10 (up):
tap10.10 (up):
  L3 192.168.1.1/24

#2、内核查询tap10.10.100信息
tap10.10.100: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.2  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::fe:91ff:fe81:fd5d  prefixlen 64  scopeid 0x20<link>
        ether 02:fe:91:81:fd:5d  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 8  bytes 696 (696.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
#3、在内核上ping网关192.168.1.1,
root@learning-vpp:~/workspace/test/qinq# ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=0.888 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=0.136 ms

接下来我们分析了ethernet-input节点如何根据报文vlan报文来匹配对应的子接口的,在函数eth_vlan_table_lookups中主要是根据报文rx接口索引sw_if_index和从以太网报头提取的字段,执行以太网子接口分类表查找,查询结果表由函数identify_subint()使用。eth_identify_subint()函数依次从qinq接口、vlan接口、物理接口default及untag来匹配,匹配上则直接返回对应表项的子接口索引,否则查询失败。

代码语言:javascript
复制
always_inline void
eth_vlan_table_lookups (ethernet_main_t * em,
            vnet_main_t * vnm,
            u32 port_sw_if_index0,
            u16 first_ethertype,
            u16 outer_id,
            u16 inner_id,
            vnet_hw_interface_t ** hi,
            main_intf_t ** main_intf,
            vlan_intf_t ** vlan_intf, qinq_intf_t ** qinq_intf)
{
  vlan_table_t *vlan_table;
  qinq_table_t *qinq_table;
  u32 vlan_table_id;

  /*分别依次读取main、vlan和qinq接口表项
TODO:考虑是否/如何预取表。还要考虑单条目缓存,以跳过表查找和identify_subint()处理。*/
  *hi = vnet_get_sup_hw_interface (vnm, port_sw_if_index0);
  *main_intf = vec_elt_at_index (em->main_intfs, (*hi)->hw_if_index);

  /* 总是读取vlan和qinq表,即使数据包上没有那么多标签。这使得查找和比较更容易(并且分支更少)。*/
  vlan_table_id = (first_ethertype == ETHERNET_TYPE_DOT1AD) ?
    (*main_intf)->dot1ad_vlans : (*main_intf)->dot1q_vlans;
  vlan_table = vec_elt_at_index (em->vlan_pool, vlan_table_id);
  *vlan_intf = &vlan_table->vlans[outer_id];

  qinq_table = vec_elt_at_index (em->qinq_pool, (*vlan_intf)->qinqs);
  *qinq_intf = &qinq_table->vlans[inner_id];
}

static_always_inline void
identify_subint (ethernet_main_t * em,
         vnet_hw_interface_t * hi,
         vlib_buffer_t * b0,
         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)
{
  u32 matched;
  ethernet_interface_t *ei = ethernet_get_interface (em, hi->hw_if_index);
  /*根据函数eth_vlan_table_lookups查询结果来依次匹配接口*/
  matched = eth_identify_subint (hi, match_flags, main_intf, vlan_intf,
                 qinq_intf, new_sw_if_index, error0, is_l2);

  if (matched)
    {
      /*执行L3 层mac filter到达L3接口的单播报文必须有一个与接口mac匹配的dmac。如果接口有STATUS_L3位set mac filter,则已经完成。*/
      if ((!*is_l2) && ei &&
      (!(ei->flags & ETHERNET_INTERFACE_FLAG_STATUS_L3)))
    {
      u64 dmacs[2];
      u8 dmacs_bad[2];
      ethernet_header_t *e0;

      e0 = (void *) (b0->data + vnet_buffer (b0)->l2_hdr_offset);
      dmacs[0] = *(u64 *) e0;

      if (vec_len (ei->secondary_addrs))
        ethernet_input_inline_dmac_check (hi, dmacs, dmacs_bad,
                          1 /* n_packets */, ei,
                          1 /* have_sec_dmac */);
      else
        ethernet_input_inline_dmac_check (hi, dmacs, dmacs_bad,
                          1 /* n_packets */, ei,
                          0 /* have_sec_dmac */);
      if (dmacs_bad[0])
        *error0 = ETHERNET_ERROR_L3_MAC_MISMATCH;
    }

      // Check for down subinterface
      *error0 = (*new_sw_if_index) != ~0 ? (*error0) : ETHERNET_ERROR_DOWN;
    }
}

/*根据vlan表查找和vlan头解析的结果,确定此数据包的子接口。首先检查最具体的匹配项。如果找到匹配的子接口,则返回1,否则返回0。*/
always_inline u32
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;

  //每次比较都检查有效标志和标签数量(包括精确匹配/非精确匹配)。首先检查qinq接口
  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;
}

下图上面流程中报文vlan信息匹配接口相关结构体。配置过程是从上到下,转发过程是从下到上依次匹配。

在实际调试过程遇到一个问题,最开始在vpp创建接口时,使用的命令行create sub-interfaces tap10 10 dot1ad 10 inner-dot1q 100 exact-match.外层使用的dot1ad(默认TPID=0x88a8),而linux内核默认携带TPID=0x8100,导致无法查询到vlan配置而丢弃报文。具体原因是因为代码22行中,通过TPID数值来获取外层vlan配置在vlan pool池中的索引的。dot1q和dot1ad分别对应dot1q_vlans和dot1ad_vlans.

参考链接:

1、https://zhuanlan.zhihu.com/p/554612685 2、https://info.support.huawei.com/info-finder/encyclopedia/zh/QinQ.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DPDK VPP源码分析 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档