1.实现使用模板插件
- 操作系统CentOS7.6
- VPP20.01
- VSCode远程编译环境
- VPP官方文档首页:https://wiki.fd.io/view/VPP
- VPP官方文档增加插件方式:
https://fd.io/docs/vpp/master/gettingstarted/developers/add_plugin.html - VPP 20.01参考手册:https://docs.fd.io/vpp/20.01/
本文基于文章VPP之浅谈插件和使用(转载)进行实现,采用vpp中提供的创建插件的脚本,创建需要的插件基本框架。注意模板插件起名时,最好不要以r
开头,否则编译会出问题。
sudo yum install -y emacs
$ cd ./src/plugins
$ ../../extras/emacs/make-plugin.sh
然后,可以通过以下命令编译运行,会发现生成myplugin.so的文件。
$ make rebuild or rebuild-release
$ make run
将该动态链接库可以直接拷贝至rpm包安装方式生成的的/lib/vpp_plugins
目录中,并重启服务systemctl restart vpp
,并查看到插件myplugin能够正常启动。这些步骤都说明,VPP中自定义插件的实现都需要基于其框架模板进行编码,才能挂载VPP结点并实现功能。因此,熟悉掌握其中框架结构,就变得很有意义。
2.插件模板目录结构
自VPP版本19以上,插件的编译已不再使用autoconf/configure/makefile等方式,而是采用CMAKE编辑,所以先要理解其代码结构。采用插件模板生成文件如下。
$ ls /vpp/src/plugins/myplugin
CMakeLists.txt myplugin.c myplugin_periodic.c setup.pg
myplugin_all_api_h.h myplugin.h myplugin_test.c
myplugin.api myplugin_msg_enum.h node.c
其中部分文件主要功能解释如下,文件myplugin.api以及test文件可以参考文章。
├──CMakeLists.txt 插件编译配置文件,包含编译插件名称、编译代码、依赖库、api文件设置等;
├──myplugin.c 结点初始化VNET_FEATURE_INIT,注册消息队列到全局;注册CLI命令及其响应函数,同时启动定时扫描进程,见periodic文件;
├──myplugin_periodic.c 定义了扫描进程函数,相关事件触发机制,监听插件是否工作;
├──myplugin_test.c 定义api方式定义的客户端vat方面代码;
├──node.c 完成结点注册VLIB_REGISTER_NODE以及插件功能实现函数VLIB_NODE_FN;
├──myplugin.api 主要是同vat客户端程序通信数据格式的接口定义,便于结构同步;可参考文章
├──myplugin_test.c vat客户端测试代码
3.源码分析
生成模板插件中的部分文件引用了VPP库中几个关键性目录中文件,可以参考如下目录;要注意的是,整个VPP的软件框架主要分为四个层面,依次执行顺序是VPP Infra->VLIB->VNET->Plugins,所以程序编码中,会以VLIB宏为基础执行,然后是VNET相关宏。
top ├──Plugins 包含越来越丰富的数据平面插件集,可以认为每一个插件是一个小型的应用app
│ │
│ ├──VNET 与VPP的网络接口(第2,3和4层)协同工作,执行会话和流量管理,并与设备和数据控制平面配合使用
│ │
│ ├──VLIB 矢量处理库。vlib层还处理各种应用程序管理功能:缓冲区,内存和图形结点管理,维护和导出计数器,线程管理,数据包跟踪。Vlib实现调试CLI(命令行界面)
│ │
bottom └──VPP Infra VPP基础设施层,包含核心库源代码。该层执行内存函数,与向量和环一起使用,在哈希表中执行键查找,并与定时器一起用于调度图结点
注册了process结点,监听插件是否工作的事件MYPLUGIN_EVENT_PERIODIC_ENABLE_DISABLE
,
通过命令行来触发VLIB_CLI_COMMAND (myplugin_enable_disable_command, static)
这个事件。
使用这里enable了,该插件才会work。
注册了内部结点,让其在ethernet-input结点运行之前运行。
3.1 注册结点:VLIB_REGISTER_NODE
在node.c的VLIB_REGISTER_NODE
中注册了内部结点myplugin
。
vlib_node_registration_t myplugin_node; //插件注册结点结构体
VLIB_REGISTER_NODE (myplugin_node) =
{
.name = "myplugin", //结点名称
.vector_size = sizeof (u32),
.format_trace = format_myplugin_trace,
.type = VLIB_NODE_TYPE_INTERNAL, //内部结点类型
.n_errors = ARRAY_LEN(myplugin_error_strings),
.error_strings = myplugin_error_strings,
.n_next_nodes = MYPLUGIN_N_NEXT,
.next_nodes = {
[MYPLUGIN_NEXT_INTERFACE_OUTPUT] = "interface-output",
},
};
3.2 结点功能实现函数:VLIB_NODE_FN
在node.c文件中,定义结点myplugin_node的实现函数VLIB_NODE_FN (myplugin_node),主要实现功能是对input结点收进来的报文,做一个src dst mac交换,然后源端口发送出去。因其参数为myplugin_node,所以与注册结点置入同一文件中。
该结点功能函数调用是在myplugin.c文件中的vnet_feature_enable_disable
函数中。
那么为什么在VLIB_NODE_FN中定义功能函数,vnet_feature_enable_disable中实现功能函数,它们是如何关联上的呢?答案在节3.4的VNET_FEATURE_INIT (myplugin, static) 函数中。
VLIB_NODE_FN (myplugin_node) (vlib_main_t * vm,
vlib_node_runtime_t * node,
vlib_frame_t * frame)
{
...
//交换mac的功能实现,这里不展开
}
3.3 注册插件名称以及描述:VLIB_PLUGIN_REGISTER
在myplugin.c的VLIB_PLUGIN_REGISTER中,描述了插件的VPP版本号以及描述等信息。
VLIB_PLUGIN_REGISTER () =
{
.version = VPP_BUILD_VER,
.description = "myplugin plugin description goes here",
};
//其实VLIB_PLUGIN_REGISTER为结构体,在vlib/unix/plugin.h中定义
typedef CLIB_PACKED(struct {
u8 default_disabled;
const char version[32];
const char version_required[32];
const char *early_init;
const char *description;
}) vlib_plugin_registration_t;
3.4 注册结点初始化1:VLIB_INIT_FUNCTION
在myplugin.c的VLIB_INIT_FUNCTION
函数中,实例化myplugin_main_t结构体,赋值各个参数,其中msg_id_base是重点,将本API消息注册到全局hash表中。
//这里是myplugin_main_t结构体定义
typedef struct {
/* API message ID base */
u16 msg_id_base;
/* on/off switch for the periodic function */
u8 periodic_timer_enabled;
/* Node index, non-zero if the periodic process has been created */
u32 periodic_node_index;
vlib_main_t * vlib_main;
vnet_main_t * vnet_main;
ethernet_main_t * ethernet_main;
} myplugin_main_t;
//这里定义VLIB初始化函数并绑定到VLIB库上
VLIB_INIT_FUNCTION (myplugin_init);
static clib_error_t * myplugin_init (vlib_main_t * vm)
{
myplugin_main_t * mmp = &myplugin_main;
clib_error_t * error = 0;
mmp->vlib_main = vm;
mmp->vnet_main = vnet_get_main();
/* Add our API messages to the global name_crc hash table */
mmp->msg_id_base = setup_message_id_table ();
return error;
}
3.4 注册结点初始化2:VNET_FEATURE_INIT
因此VLIB与VNET是层级调用关系,所以在myplugin.c的VNET_FEATURE_INIT
初始化,让其在ethernet-input结点运行之前运行。此处第一参数myplugin结构体的定义在vnet/feature/feature.h中。
这一步中,将VLIB中定义的myplugin功能函数通过.node_name以及.arc_name联系起来,因此调用3.2节的结点功能函数时,使用vnet_feature_enable_disable ("device-input", "myplugin",sw_if_index, enable_disable, 0, 0);
,函数参数是个重点。
/* *这里面对结点myplugin的初始化,让其* */
VNET_FEATURE_INIT (myplugin, static) =
{
.arc_name = "device-input",
.node_name = "myplugin",
.runs_before = VNET_FEATURES ("ethernet-input"),
};
/* *INDENT-ON */
3.5 注册结点的CLI命令及激活:VLIB_CLI_COMMAND
通过命令行来触发VLIB_CLI_COMMAND (myplugin_enable_disable_command, static)事件,CLI命令是myplugin enable-disable eth0
,挂载成功后该插件才会work。关闭某端口的该功能myplugin enable-disable eth0 disable
。
//这里是注册CLI响应函数的接口,.funtion指向了实现函数。
VLIB_CLI_COMMAND (myplugin_enable_disable_command, static) =
{
.path = "myplugin enable-disable",
.short_help =
"myplugin enable-disable <interface-name> [disable]",
.function = myplugin_enable_disable_command_fn,
};
//该函数嵌套一层操作返回判断,最后执行了myplugin_enable_disable函数
static clib_error_t *
myplugin_enable_disable_command_fn (...)
{ ...
rv = myplugin_enable_disable (mmp, sw_if_index, enable_disable);
switch(rv){...}
return 0;
}
//真正响应CLI命令的函数
int myplugin_enable_disable (myplugin_main_t * mmp, u32 sw_if_index,int enable_disable)
{
...
//创建监控进程
myplugin_create_periodic_process (mmp);
//执行结点功能函数
vnet_feature_enable_disable ("device-input", "myplugin",
sw_if_index, enable_disable, 0, 0);
//给监控程序发送开关插件事件
vlib_process_signal_event (mmp->vlib_main,
mmp->periodic_node_index, MYPLUGIN_EVENT_PERIODIC_ENABLE_DISABLE,
(uword)enable_disable);
return rv;
}
3.6 创建监听线程响应事件
在执行上述CLI命令中,真实实现函数myplugin_enable_disable
里面创建了监控进程,查看插件是否工作,并响应事件。真实的实现函数在文件myplugin_periodic.c中。之后具体事件处理不详细罗列。
//创建监听进程,用于响应所有开启myplugin功能的接口的事件。
void myplugin_create_periodic_process (myplugin_main_t *mmp)
{
/* Already created the process node? */
if (mmp->periodic_node_index > 0)
return;
/* No, create it now and make a note of the node index */
mmp->periodic_node_index = vlib_process_create (mmp->vlib_main,
"myplugin-periodic-process",
myplugin_periodic_process, 16 /* log2_n_stack_bytes */);
}
3.7 完成测试VAT客户端消息响应函数
myplugin.c文件中具有API消息处理函数,应该是结合测试程序VAT使用的。
该函数主要作为VPP服务端用于开启关闭插件功能,VAT客户端实现见myplugin_test.c文件,同时它们之间交互接口的定义见C语言的myplugin.api。
/* API消息处理函数 */
static void vl_api_myplugin_enable_disable_t_handler
(vl_api_myplugin_enable_disable_t * mp)
{
...
rv = myplugin_enable_disable (mmp, ntohl(mp->sw_if_index),(int) (mp->enable_disable));
...
}
您好 测试代码从哪下载,谢谢
你好,代码就在VPP 的github源,可能现在改版,需要您回退到20.05或者。01吧