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
开头,否则编译会出问题。
1 2 3 |
sudo yum install -y emacs $ cd ./src/plugins $ ../../extras/emacs/make-plugin.sh |
然后,可以通过以下命令编译运行,会发现生成myplugin.so的文件。
1 2 |
$ make rebuild or rebuild-release $ make run |
将该动态链接库可以直接拷贝至rpm包安装方式生成的的/lib/vpp_plugins
目录中,并重启服务systemctl restart vpp
,并查看到插件myplugin能够正常启动。这些步骤都说明,VPP中自定义插件的实现都需要基于其框架模板进行编码,才能挂载VPP结点并实现功能。因此,熟悉掌握其中框架结构,就变得很有意义。
2.插件模板目录结构
自VPP版本19以上,插件的编译已不再使用autoconf/configure/makefile等方式,而是采用CMAKE编辑,所以先要理解其代码结构。采用插件模板生成文件如下。
1 2 3 4 |
$ 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文件可以参考文章。
1 2 3 4 5 6 7 |
├──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相关宏。
1 2 3 4 5 6 7 |
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
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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) 函数中。
1 2 3 4 5 6 7 |
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版本号以及描述等信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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表中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
//这里是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);
,函数参数是个重点。
1 2 3 4 5 6 7 8 |
/* *这里面对结点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
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
//这里是注册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中。之后具体事件处理不详细罗列。
1 2 3 4 5 6 7 8 9 10 11 |
//创建监听进程,用于响应所有开启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。
1 2 3 4 5 6 7 8 |
/* 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)); ... } |
4.流程图归纳
微信赞赏
支付宝赞赏
您好 测试代码从哪下载,谢谢
你好,代码就在VPP 的github源,可能现在改版,需要您回退到20.05或者。01吧