前面一篇文章《learning:vpp实现dot1q终结功能配置》介绍了vlan dot1q
终结子接口功能配置,下面参考vpp官方文档介绍一下创建vlan子接口的命令行。我们都知道vpp默认都是从物理或虚拟主接口收包,那么vpp如何识别vlan
报文并将报文转发至vlan
子接口上进行业务处理,本文将逐步展开学习。
创建vlan
子接口的命令行如下所示:该命令用于为接口(也称为子接口)添加VLAN ID
。此命令的主要输入是interface
和subId (子接口 ID)参数。如果我们不指定VLAN ID
,那么VLAN ID
等于subId
,当然VLAN ID
和subId
也可以配置不同,但是一般不建议这样配置。
create sub-interfaces <interface> {<subId> [default|untagged]} | {<subId>-<subId>} | {<subId> dot1q|dot1ad <vlanId>|any [inner-dot1q <vlanId>|any] [exact-match]}
vpp中提供了一个命令行设置接口tag数值,从代码上分析此设置转发中并没有使用的,可能是为了当接口vlan ID
和subID
配置不同时,可以通过命令行查询到tag数值。
DBGvpp# set interface tag tap10.12 10
DBGvpp# show interface tap10.12 tag verbose
tap10.12: 10
该创建子接口命令行有许多种变体:
# 创建子接口来处理具有给定 802.1q VLAN ID(与 的值相同subId)的数据包。
create sub-interfaces <interface> <subId>
# 添加该 default参数表示VLAN ID 与任何其他子接口都不匹配的数据包应发送到该子接口。
create sub-interfaces <interface> <subId> default
# 添加该 untagged参数表示不应将没有VLAN ID 的数据包发送到该子接口。
create sub-interfaces <interface> <subId> untagged
# 批量创建创建vlan子接口
create sub-interfaces <interface> <subId>-<subId>
# 使用此命令指定外部VLAN ID,可以是显式的,也可以使 VLAN ID 不同于subId.
create sub-interfaces <interface> <subId> dot1q|dot1ad < vlan Id>|any [exact-match]
# 使用此命令指定外部VLAN ID 和内部 VLAN ID,也就是qinq接口
create sub-interfaces <interface> <subId> dot1q|dot1ad < vlan Id>|any inside-dot1q <vlanId>|any [exact-match] -
当显式输入dot1q
或dot1ad
,子接口可以配置为精确匹配或非精确匹配。CLI 默认设置为非精确匹配。如果exact-match
指定,则数据包必须具有与配置相同数量的vlan tag
。对于非精确匹配,数据包必须至少有该数量的标签。L3(路由)接口必须配置为完全匹配。L2 接口通常配置为非精确匹配。如果未输入dot1q
或dot1ad
,则默认行为是完全匹配exact-match
模式。
802.1q(dot1q)
和802.1ad(QINQ)
区别:
802.1Q
就是我们常说的dot1q
,IEEE 802.1Q
-英文缩写写为dot1q
。是vlan的一种封装方式(插入适合的VLAN标记)。dot就是点的意思,就简写为dot1q
了。
qinq
就是在vlan的外层再封装一个vlan,扩充vlan数量,主要用途为电信供应商可以放置一个vlan标签作为通过外部网络的辨识,而不需变动客户的封包所带出的vlan标签。
dot1q
和dot1ad
的TPID
(Tag Protocol Identifier
,标签协议标识符)有不同的值。而dot1ad
的TPID
则有所不同,根据证据,802.1ad
提出了使用0x88a8
作为TPID
,但也有提到Cisco使用了0x9200
,并且有人提议将Dot1AD
的TPID设置为0x9100
。因此,可以得出结论,dot1q
的TPID为0x8100
,而dot1ad
的TPID可能有不同的实现,包括但不限于0x88a8、0x9200
或0x9100
。目前vpp版本中判断报文是否携带tag,以上TPID都进行识别,代码如下:
static_always_inline int
ethernet_frame_is_tagged (u16 type)
{
#ifdef CLIB_HAVE_VEC128
return !u16x8_is_all_zero (tagged_ethertypes == u16x8_splat (type));
#else
if ((type == ETHERNET_TYPE_VLAN) /*8100*/||
(type == ETHERNET_TYPE_DOT1AD) /*88a8*/||
(type == ETHERNET_TYPE_VLAN_9100) || (type == ETHERNET_TYPE_VLAN_9200))
return 1;
#endif
return 0;
}
下面我们在腾讯云主机环境来分别验证一下上面配置的不同。创建一个tap10
接口,并创建VLAN子接口 10 来处理 802.1q VLAN ID 10
上的数据包的示例,在vppctl
命令行视图配置如下:
#创建一个tap10接口
create tap id 10 host-if-name tap10
#创建一个VLan id 10的子接口,并设置接口up及配置ip地址。
create sub-interfaces tap10 10
set interface state tap10 up
set interface state tap10.10 up
set interface ip address tap10.10 192.168.1.1/24
在vpp中接口默认情况下是L3_mode,当接口加入到网桥(二层域)中会切换到L2_mode。子接口只有在exact-match模式下,才允许配置ip地址的。下面我们尝试在非exact-match模式下,配置ip地址时提示错误。 DBGvpp# create tap id 10 host-if-name tap10 tap10 DBGvpp# create sub-interfaces tap10 10 dot1q 10 tap10.10 DBGvpp# set interface ip addr tap10.10 192.168.1.1/24 set interface ip address: sub-interface without exact-match doesn't support IP addressing
在linux内核配置如下:
ip link add link tap10 name tap10.10 type vlan id 10
ip link set tap10.10 up
ip addr add 192.168.1.2/24 dev tap10.10
接口在内核发起ping 网关192.168.1.1,可以正常ping通,这里就不再展示了。上图中我们使用命令create sub-interfaces tap10 10
来创建一个vlan id 10的子接口tap10.10,此命令行等同于create sub-interfaces tap10 10 dot1q 10 exact-match
这里就不再测试了。
下面我们从代码层面来分析创建子接口函数调用及关键数据结构体说明。下面是在命令行中输入create sub-interfaces
的之后,完成解析命令行输入参数调用函数create_sub_interfaces
完成子接口的创建及vlan资源的申请及初始化。
#命令行接口子接口配置并进入创建子接口流程。
create_sub_interfaces()
#创建子接口入口函数
vnet_create_sw_interface()
#向vnet接口管理模块申请vnet_sw_interface_t存储区,不触发接口添加删除回调函数
vnet_create_sw_interface_no_callbacks()
#触发接口添加和删除的回调函数,完成ethernet子接口属性设置。
vnet_sw_interface_set_flags_helper()
ethernet_sw_interface_add_del()
#配置子接口相关属性
ethernet_sw_interface_get_config()
我梳理完了创建子接口的相关结构体关系,将结构体分成了配置数据和转发数据结构体。配置数据创建了当前子接口的软件接口资源sw_interfaces
并根据命令行参数完成vnet_sub_interface_t sub
结构的设置。转发数据存储结构是在ethernet_input node
节点通过输入的报文tag信息查询到匹配的配置,从而找到对应的接口索引,将vlib_buffer_t
结构接口rx赋值当前匹配的接口(也就是下面标红的存储的接口索引),再送入下一个节点处理。
这里有一个关键的信息,就是sunint_config_t
存储的接口索引是在什么时候完成赋值的。在创建子接口阶段通过ethernet_sw_interface_add_del
函数调用ethernet_sw_interface_get_config
函数之后对接口索引进行赋值~0(因为接口状态默认初始化为down状态),flags信息缺省是L3模式。
当我们通过设置接口状态up时,会触发响应的回调函数ethernet_sw_interface_up_down
,查询接口对应子接口信息,将sunint_config_t
结构中sw_if_index
接口进行赋值。
至此我们完成了创建子接口的相关代码及结构体的分析。文章中的结构体关系图放在个人git仓库https://github.com/jin13417/dpdk-vpp-learning感兴趣的可以自取。分析有问题的地方请各位指正。
本文分享自 DPDK VPP源码分析 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!