VPP功能模块弧形架构解析
最近项目中需要增加一个feature arc类用于挂接在自定义的收包节点上,完成类似ip4-output feature arc功能。但是每次使能后都会出现异常,最后发现是feature arc类结束节点没有进行初始化导致问题。下来我们就来研究一下vpp的feature机制。
简介
VPP内部业务逻辑是通过一系列的node连接来实现的,这些node通常在初始化时就已经定义,比如二层以太处理ethernet-input,三层ip4-input等,通过初始化定义,将node连接成一个有序的向量图,来实现VPP的业务功能,如下图所示:
而早期的VPP本身的node框架比较固定,各个node之间逻辑连接已经固定了。为此新版本增加了feature机制,每个feature是一个node,用户可以启用/停止某个或某些feature。用户也可以自己写插件,把自定义node(自己的业务逻辑)加入到指定位置。 VPP中,将不同的feature按照类型分成了不同的组,每组feature称之为一个arc。arc中的feature按照代码指定的顺序串接起来。arc结构中,记录这组feature中的起始node和结束node。系统初始化时,会完成初步的排序,但并没有应用到对应的接口中。
feature arc及feature注册
下面以ip4-unicast为例来介绍基本的流程:
代码位置:vnet\ipIp4_forward.c feature arc及feature的main函数启动前的注册宏
1、feature arc注册
VNET_FEATURE_ARC_INIT将注册feature arc,主要初始化feature类的名称、起始及结束node名字及记录arc索引的指针地址。会 以链表形式挂接到全局变量extern vnet_feature_main_t feature_main 的next_arc上。
/* Built-in ip4 unicast rx feature path definition */
VNET_FEATURE_ARC_INIT (ip4_unicast, static) =
{
.arc_name = "ip4-unicast",
.start_nodes = VNET_FEATURES ("ip4-input", "ip4-input-no-checksum"),
.last_in_arc = "ip4-lookup",
.arc_index_ptr = &ip4_main.lookup_main.ucast_feature_arc_index,
};
2、feature注册
VNET_FEATURE_INIT完成feature的注册,主要初始化arc名称,node节点名称,及运行当前feature node的前面或后面的feature node。
VNET_FEATURE_INIT (ip4_policer_classify, static) =
{
.arc_name = "ip4-unicast",
.node_name = "ip4-policer-classify",
.runs_before = VNET_FEATURES ("ipsec4-input-feature"),
};
VNET_FEATURE_INIT (ip4_lookup, static) =
{
.arc_name = "ip4-unicast",
.node_name = "ip4-lookup",
.runs_before = 0, /* not before any other features */
};
需要注意是feature的顺序是初始化的时候已经固定固定好的。 ip4-unicast以ip4-lookup结束,虽然在feature arc 初始化时已经指定,但是在feature 初始化时必须设置一下,否则会引起转发异常(debug版本直接coredump,release版本可能转发节点异常)。
feature初始化及使能
1、feature初始化 feature初始化流程必须是在node节点注册的后面,是由VLIB_INIT_FUNCTION (vnet_feature_init)宏注册到初始化函数中,在mian loop前被调用。主要是完成feature相关资源的申请及初始化,遍历vnet_feature_main_t feature_main链表生成feature arc及生成feature资源顺序关系图。
2、以ip4-unicast为例,vnet_feature_arc_init函数实现流程。
typedef struct
/** feature arc configuration list
*main函数启动前VNET_FEATURE_ARC_INIT宏中注册的feature arc 链表形式保存下来*/
vnet_feature_arc_registration_t *next_arc;
/*创建<key:arc_name, vlaue:feature_arc指针及在next_arc链表中的节点地址>的hash表*/
uword **arc_index_by_name;
/** feature path configuration lists
*main函数启动前 VNET_FEATURE_INIT宏将feature注册到这个链表中*/
vnet_feature_registration_t *next_feature;
/*next_feature_by_arc[arc_index] 将同一类的feature以链表形式串到一起*/
vnet_feature_registration_t **next_feature_by_arc;
/*vector结构:二维,一维是feature arc的索引,二维是hash头
next_feature_by_name[arc_index] : <key:feature结构中node 名称;
value: vnet_feature_registration_t 的指针。 */
uword **next_feature_by_name;
/** feature arc类对应一个 vnet_feature_config_main_t。
*feature_config_mains[arc_index]:保存feature类的配置属性*/
vnet_feature_config_main_t *feature_config_mains;
/** Save partial order results for show command
*feature_nodes[x], X:feature arc 的索引
保存feature 节点,保存节点node的名称,顺序存储*/
char ***feature_nodes;
/** bitmap of interfaces which have driver rx features configured */
uword **sw_if_index_has_features;
/** feature reference counts by interface
*feature_count_by_sw_if_index[arc_index][sw_if_index]:下面feature的计数*/
i16 **feature_count_by_sw_if_index;
/** Feature arc index for device-input */
u8 device_input_feature_arc_index;
/** convenience */
vlib_main_t *vlib_main;
vnet_main_t *vnet_main;
} vnet_feature_main_t;
关键结构体如下:
1、vnet_feature_config_main_t 该结构是所有feature相关的配置主结构体,每个arc有一个对应的该结构体。在为接口开启feature时,会创建对应的接口体,并按照接口索引设置config_index_by_sw_if_index这个vec向量,保存配置的索引。 2、vnet_config_main_t feature配置主结构体,其中config_pool是配置结构的内存池,不同的接口如果设置了不同的feature就会申请不同的vnet_config_t。vnet_feature_init启动中完成对feature的排序、feature和node节点索引之间的关系。 3、vnet_config_t feature的配置结构体,里面保存了多个feature,当接口使能一个feature时,会在对应的feature中增加多个vnet_config_feature_t。该结构体中还在config_string中保存了feature之间的node关系。 4、vnet_config_feature_t config中的具体的feature,当为接口配置feature时,将会为该配置最终生成此转发数据结构,用于生成转发顺序图
3、使能去使能流程feature
#dhcp报文探测报文处理节点去使能
vnet_feature_enable_disable ("ip4-unicast",
"ip4-dhcp-client-detect",
c->sw_if_index, 0 /* disable */ , 0, 0);
feature使能去使能函数处理逻辑如下:
4、最终生成转发节点获取next0结构图
结构体vnet_feature_config_main_t中变量config_index_by_sw_if_index是vector结构,通过接口索引获取config_index(也就是在config_string_heap堆的偏移量)。
下面函数就是在转发获取next0,实际上就是从config_String中获取。
always_inline void *
vnet_get_config_data (vnet_config_main_t * cm,
u32 * config_index, u32 * next_index, u32 n_data_bytes)
{
u32 i, n, *d;
/*获取当前config索引*/
i = *config_index;
d = heap_elt_at_index (cm->config_string_heap, i); /*字节对齐操作*/
n = round_pow2 (n_data_bytes, sizeof (d[0])) / sizeof (d[0]);
/* Last 32 bits are next index. */
*next_index = d[n];
/* Advance config index to next config. 更新到下一个node节点对应的config索引*/
*config_index = (i + n + 1);
/* Return config data to user for this feature. 返回当前节点配置数据起始指针*/
return (void *) d;
}
相关命令行
1、show feature [verbose]
可以查询arc的索引及当前arc类型所有feature的排序顺序
show features verbose
[] ip4-unicast: #ipv4单播 arc索引是16
[]: ip4-rx-urpf-loose
[]: ip4-rx-urpf-strict
[]: svs-ip4
....
[]: ip4-vxlan-bypass
[]: ip4-lookup #ip4-lookup目前在最后,
这里只是ip4-unicast单播的feature挂接顺序,如果未使能的话,并不会挂接在ip4-input的下面。vpp是动态挂feature的。
2、show node ip4-input
查询当前node节点下feature的信息,node类型、node的状态及索引等。
vpp# show node ip4-input
#node 类型,状态 及索引
node ip4-input, type internal, state active, index 567
node function variants:
Name Priority Active
default 0
icl -1
skx -1
hsw 50 yes
next nodes:#当前node已经挂接的孩子node节点情况:
next-index node-index Node Vectors
0 649 error-drop
error-punt
ip4-options
ip4-lookup
ip4-mfib-forward-lookup
ip4-icmp-error
ip4-full-reassembly
ip4-not-enabled
known previous nodes:#当前节点的父节点
vmxnet3-input () rdma-input () pppoe-input ()
这里也可以通过show vlib graph ip4-input来查询node 图。
3、 show interface feat
查询指定接口下所有arc的使能情况:
show interface feat GigabitEthernet13//
ip4-unicast:
ip4-sv-reassembly-feature
nat44-in2out
上面是接口使能nat44功能,会同时使能ip报文重组功能,用于解决分片报文nat五元组查询问题。
4、set interface feature
#指定接口使能某arc类下feature功能。
set interface feature <intfc> <feature_name> arc <arc_name> [disable]
注意,arc类下必须已经注册当前feature,否则会失败。
定义自己的feature arc
1、自定义arc类及自定义业务feature。
#1、注册arc类internal_rx_output_v4
VNET_FEATURE_ARC_INIT (internal_rx_output_v4, static) =
{
.arc_name = "internal_rx_output_v4",
.start_nodes = VNET_FEATURES ("internal-rx"),
.last_in_arc = "interface-output",
.arc_index_ptr = &internal_main.outputv4_feature_arc_index,
};
#2、注册arc类最后一个节点 ,这个必须设置。
VNET_FEATURE_INIT (internal_rx_ip4_interface_output, static) =
{
.arc_name = "internal_rx_output_v4",
.node_name = "interface-output",
.runs_before = , /* not before any other features */
};
#3、将自己新增ip4_test_output的node节点挂接到arc类中
VNET_FEATURE_INIT (ip4_test_output, static) = {
.arc_name = "internal_rx_output_v4",
.node_name = "ip4_test_output",
.runs_before = VNET_FEATURES ("interface-output"),
};
2、新增node节点internal-rx-input作为feature arc的起始node
在internat-rx-input节点增加自己的业务处理逻辑,然后判断接口是否使能feature,如果使能调用函数vnet_feature_arc_start设置vlib_buffer_t结构中feature_arc_index及当前current_config_index索引。
u32 arc_index = internal_main.outputv4_feature_arc_index;
if (vnet_have_features (arc_index, sw_if_index))
{
/**/
vnet_feature_arc_start (arc_index, sw_if_index, &next, b);
}
else
{
next = interface_output_index;
}
3、arc中中间node节点获取next
调用函数vnet_feature_next从config_string中读取当前node的子节点的slot num 赋值next0,报文最终送到子节点处理。
vnet_feature_next (u32 * next0, vlib_buffer_t * b0)
总结
本文简单描述了vpp的feature机制的注册、初始化及函数调用,并介绍了如何注册
和使用自己的feature arc类。通过VPP的feature机制,可以在不改变VPP现有框架
下,灵活地增加/删除业务功能。
巨人的肩膀
1、vpp feature机制介绍 https://www.sdnlab.com/24055.html 2、vppfeature arc说明 https://www.yuque.com/taohuaban/fc6dp0/ix0fkg
推荐阅读