目录
1、VLIB_INIT_FUNCTION(ck_sample_init)
插件运行
pkt dump加网卡名称,开启插件功能,该插件打印数据包的前34个字节
插件初始化
ck_sample_main_t ck_sample_main;
static clib_error_t *ck_sample_init(vlib_main_t* vm)
{
ck_sample_main.vnet_main = vnet_get_main();
return 0;
}
VLIB_INIT_FUNCTION(ck_sample_init);
1、VLIB_INIT_FUNCTION(ck_sample_init)
/* Declaration is global (e.g. not static) so that init functions can
be called from other modules to resolve init function depend. */
#define VLIB_DECLARE_INIT_FUNCTION(x, tag) \
vlib_init_function_t * _VLIB_INIT_FUNCTION_SYMBOL (x, tag) = x; \
static void __vlib_add_##tag##_function_##x (void) \
__attribute__((__constructor__)) ; \
static void __vlib_add_##tag##_function_##x (void) \
{ \
vlib_main_t * vm = vlib_get_main(); \
static _vlib_init_function_list_elt_t _vlib_init_function; \
_vlib_init_function.next_init_function \
= vm->tag##_function_registrations; \
vm->tag##_function_registrations = &_vlib_init_function; \
_vlib_init_function.f = &x; \
}
#define VLIB_INIT_FUNCTION(x) VLIB_DECLARE_INIT_FUNCTION(x,init)
1)该宏定义了一个函数指针在VPP/vpp/src/vlib/init.h头文件中
vlib_init_function_t * _vlib_init_function_ck_sample_init = ck_sample_init
2)声明函数static void __vlib_add_init_function_ck_sample_init(void) __attribute__((__constructor__)) ;
__attribute__ ((constructor))会使函数在main()函数之前被执行
__attribute__ ((destructor))会使函数在main()退出后执行
注:全局变量对象的构造函数和析构函数分别在__attribute__((constructor))和__attribute__((destructor))标志的函数之前调用。
3)__vlib_add_init_function_ck_sample_init(void)函数实现如下:
获取vlib_main_t * vm = vlib_get_main();
将vm->init_function_registrations是一个链表,每个元素是一个插件初始化函数
定义static _vlib_init_function_list_elt_t _vlib_init_function;
_vlib_init_function.f = &ck_sample_init
将_vlib_init_function插入vm->init_function_registrations链表头
2、ck_sample_init
调用vnet_get_main()获取VNET主结构体地址,并赋值给ck_sample_main.vnet_main
插件注册
VLIB_PLUGIN_REGISTER () = {
.version = CK_SAMPLE_PLUGIN_BUILD_VER,
.description = "Sample of VPP Plugin",
};
#define VLIB_PLUGIN_REGISTER() \
vlib_plugin_registration_t vlib_plugin_registration \
__attribute__((__section__(".vlib_plugin_registration")))
-
vlib_plugin_registration_t vlib_plugin_registration
: 这里声明了一个类型为vlib_plugin_registration_t
的变量vlib_plugin_registration
。vlib_plugin_registration_t
是一个结构体类型,用于存储插件的元数据,比如插件的版本、描述等。 -
__attribute__((__section__(".vlib_plugin_registration")))
: 这是 GCC 编译器的一个特性,允许开发者将变量放置在指定的内存段中。在这个例子中,vlib_plugin_registration
变量被放置在.vlib_plugin_registration
段中
当 VPP 启动时,它会遍历 .vlib_plugin_registration
段,收集所有已注册的插件信息。这通常是在初始化阶段完成的。
feature初始化
VNET_FEATURE_INIT(ck_sample, static) =
{
.arc_name = "ip4-unicast",
.node_name = "ck_sample",
.runs_before = VNET_FEATURES("ip4-lookup"),
};
#define VNET_FEATURE_INIT(x,...) \
__VA_ARGS__ vnet_feature_registration_t vnet_feat_##x; \
static void __vnet_add_feature_registration_##x (void) \
__attribute__((__constructor__)) ; \
static void __vnet_add_feature_registration_##x (void) \
{ \
vnet_feature_main_t * fm = &feature_main; \
vnet_feat_##x.next = fm->next_feature; \
fm->next_feature = & vnet_feat_##x; \
} \
__VA_ARGS__ vnet_feature_registration_t vnet_feat_##x
1、这里是注册了一个feature
.arc_name表示该feature属于哪个arc,arc的概念相当于一个group,里面有多个feature
.node_name表示该feature控制的node
.runs_before表示ck_sample这个node的优先级比ip4-lookup高
2、VNET_FEATURE_INIT宏
定义static vnet_feature_registration_t vnet_feat_ck_sample
定义函数
static void __vnet_add_feature_registration_ck_sample (void) __attribute__((__constructor__))
__vnet_add_feature_registration_ck_sample函数中将vnet_feat_ck_sample添加到feature_main链表中
命令初始化
int ck_sample_enable_disable(u32 sw_if_index, int enable)
{
//用于检查 sw_if_index 是否是一个有效的接口索引。
if (pool_is_free_index (ck_sample_main.vnet_main->interface_main.sw_interfaces,
sw_if_index))
return VNET_API_ERROR_INVALID_SW_IF_INDEX;
/*
vnet_feature_enable_disable 是 VPP 中用于启用或禁用特性的函数。
参数 "ip4-unicast" 指定了特性所属的特性链(Feature Arc),即 IPv4 单播处理链。
数 "ck_sample" 指定了要启用/禁用的特性名称。
参数 sw_if_index 指定了要操作的接口。
参数 enable 表示启用(1)或禁用(0)特性。
最后两个 0 参数通常用于传递特性启用/禁用时的附加数据或标志,但在这里未使用。
*/
vnet_feature_enable_disable("ip4-unicast",
"ck_sample",
sw_if_index, enable, 0, 0);
return 0;
}
static clib_error_t*
ck_sample_enable_disable_command_fn(vlib_main_t* vm,
unformat_input_t *input,
vlib_cli_command_t *cmd)
{
u32 sw_if_index = ~0;
int enable_disable = 1;
//用于检查输入流是否结束。
while(unformat_check_input(input) != UNFORMAT_END_OF_INPUT) {
//检查输入中是否包含 "disable",如果存在则将 enable_disable 设置为 0,表示禁用特性。
if (unformat(input, "disable"))
enable_disable = 0;
//解析接口名称并获取对应的接口索引 sw_if_index。
else if (unformat(input, "%U",
unformat_vnet_sw_interface,
ck_sample_main.vnet_main, &sw_if_index));
else
break;
}
//必须指定网卡
if (sw_if_index == ~0)
return clib_error_return(0, "Please specify an interface...");
ck_sample_enable_disable(sw_if_index, enable_disable);
return 0;
}
//添加命令pkt dump
VLIB_CLI_COMMAND (ck_sample_command, static) = {
.path = "pkt dump",
.short_help =
"pkt dump <interface-name> [disable]",
.function = ck_sample_enable_disable_command_fn,
};
VLIB_CLI_COMMAND
定义了static vlib_cli_command_t ck_sample_command变量
定义函数
static void __vlib_cli_command_registration_ck_sample_command (void) \
__attribute__((__constructor__)) ;
该函数将ck_sample_command加入vlib_get_main()获取的链表中
&(vlib_main_t->cli_main)->cli_command_registrations
#define VLIB_CLI_COMMAND(x,...) \
__VA_ARGS__ vlib_cli_command_t x; \
static void __vlib_cli_command_registration_##x (void) \
__attribute__((__constructor__)) ; \
static void __vlib_cli_command_registration_##x (void) \
{ \
vlib_main_t * vm = vlib_get_main(); \
vlib_cli_main_t *cm = &vm->cli_main; \
x.next_cli_command = cm->cli_command_registrations; \
cm->cli_command_registrations = &x; \
}
注册节点
VLIB_REGISTER_NODE (ck_sample_node) = {
.name = "ck_sample", //节点名称
.function = ck_sample_node_fn, //节点的处理函数
.vector_size = sizeof(u32), //每个向量(即每个数据包索引)所占用的内存大小,这里是 sizeof(u32)。
.format_trace = format_ck_sample_trace, //用于格式化节点 trace 信息的函数
.type = VLIB_NODE_TYPE_INTERNAL, //节点类型,这里是内部节点 VLIB_NODE_TYPE_INTERNAL。
.n_errors = ARRAY_LEN(ck_sample_error_strings), //节点可能的错误类型数量
.error_strings = ck_sample_error_strings, //错误类型对应的错误字符串
.n_next_nodes = CK_SAMPLE_NEXT_N, //下一个节点的数量,这里是 CK_SAMPLE_NEXT_N
.next_nodes = { //下一个节点的映射
[CK_SAMPLE_NEXT_IP4] = "ip4-lookup",
[CK_SAMPLE_DROP] = "error-drop",
},
};
#define VLIB_REGISTER_NODE(x,...) \
__VA_ARGS__ vlib_node_registration_t x; \
static void __vlib_add_node_registration_##x (void) \
__attribute__((__constructor__)) ; \
static void __vlib_add_node_registration_##x (void) \
{ \
vlib_main_t * vm = vlib_get_main(); \
x.next_registration = vm->node_main.node_registrations; \
vm->node_main.node_registrations = &x; \
} \
__VA_ARGS__ vlib_node_registration_t x
定义了static vlib_node_registration_t ck_sample_node变量
定义函数
static void __vlib_add_node_registration_ck_sample_command (void) \
__attribute__((__constructor__)) ;
该函数将ck_sample_node加入vlib_get_main()获取的链表中
vlib_main_t->node_main.node_registrations
代码
vpp/src/plugins下新增pktdump目录,存放pktdump.c, pktdump.h, pktdump_node.c
pktdump.c
#include <vnet/plugin/plugin.h>
#include <pktdump/pktdump.h>
ck_sample_main_t ck_sample_main;
int ck_sample_enable_disable(u32 sw_if_index, int enable)
{
//用于检查 sw_if_index 是否是一个有效的接口索引。
if (pool_is_free_index (ck_sample_main.vnet_main->interface_main.sw_interfaces,
sw_if_index))
return VNET_API_ERROR_INVALID_SW_IF_INDEX;
/*
vnet_feature_enable_disable 是 VPP 中用于启用或禁用特性的函数。
参数 "ip4-unicast" 指定了特性所属的特性链(Feature Arc),即 IPv4 单播处理链。
数 "ck_sample" 指定了要启用/禁用的特性名称。
参数 sw_if_index 指定了要操作的接口。
参数 enable 表示启用(1)或禁用(0)特性。
最后两个 0 参数通常用于传递特性启用/禁用时的附加数据或标志,但在这里未使用。
*/
vnet_feature_enable_disable("ip4-unicast",
"ck_sample",
sw_if_index, enable, 0, 0);
return 0;
}
static clib_error_t*
ck_sample_enable_disable_command_fn(vlib_main_t* vm,
unformat_input_t *input,
vlib_cli_command_t *cmd)
{
u32 sw_if_index = ~0;
int enable_disable = 1;
//用于检查输入流是否结束。
while(unformat_check_input(input) != UNFORMAT_END_OF_INPUT) {
//检查输入中是否包含 "disable",如果存在则将 enable_disable 设置为 0,表示禁用特性。
if (unformat(input, "disable"))
enable_disable = 0;
//解析接口名称并获取对应的接口索引 sw_if_index。
else if (unformat(input, "%U",
unformat_vnet_sw_interface,
ck_sample_main.vnet_main, &sw_if_index));
else
break;
}
//必须指定网卡
if (sw_if_index == ~0)
return clib_error_return(0, "Please specify an interface...");
ck_sample_enable_disable(sw_if_index, enable_disable);
return 0;
}
//添加命令pkt dump
VLIB_CLI_COMMAND (ck_sample_command, static) = {
.path = "pkt dump",
.short_help =
"pkt dump <interface-name> [disable]",
.function = ck_sample_enable_disable_command_fn,
};
VLIB_PLUGIN_REGISTER () = {
.version = CK_SAMPLE_PLUGIN_BUILD_VER,
.description = "Sample of VPP Plugin",
};
static clib_error_t *ck_sample_init(vlib_main_t* vm)
{
ck_sample_main.vnet_main = vnet_get_main();
return 0;
}
VLIB_INIT_FUNCTION(ck_sample_init);
VNET_FEATURE_INIT(ck_sample, static) =
{
.arc_name = "ip4-unicast",
.node_name = "ck_sample",
.runs_before = VNET_FEATURES("ip4-lookup"),
};
pktdump.h
#ifndef __included_ck_sample_h__
#define __included_ck_sample_h__
#include <vnet/vnet.h>
#include <vnet/ip/ip.h>
#include <vppinfra/hash.h>
#include <vppinfra/error.h>
#include <vppinfra/elog.h>
typedef struct {
/* API message ID base */
u16 msg_id_base;
/* convenience */
vnet_main_t * vnet_main;
} ck_sample_main_t;
extern ck_sample_main_t ck_sample_main;
extern vlib_node_registration_t ck_sample_node;
#define CK_SAMPLE_PLUGIN_BUILD_VER "1.0"
#endif /* __included_ck_sample_h__ */
pktdump_node.c
#include <vlib/vlib.h>
#include <vnet/vnet.h>
#include <vnet/pg/pg.h>
#include <vnet/ethernet/ethernet.h>
#include <vppinfra/error.h>
#include <pktdump/pktdump.h>
typedef enum
{
CK_SAMPLE_NEXT_IP4, //数据包将被传递到 ip4-lookup 节点进行 IP 处理。
CK_SAMPLE_DROP, //数据包将被丢弃,传递到 error-drop 节点。
CK_SAMPLE_NEXT_N, //用于表示“下一步”节点的数量。
} ck_sample_next_t;
typedef struct
{
u32 next_index;
u32 sw_if_index;
u8 new_src_mac[6];
u8 new_dst_mac[6];
} ck_sample_trace_t;
#define foreach_ck_sample_error \
_(SHOWED, "show packets processed")
typedef enum
{
#define _(sym,str) SAMPLE_ERROR_##sym,
foreach_ck_sample_error
#undef _
SAMPLE_N_ERROR,
} ck_ssample_error_t;
static char *ck_sample_error_strings[] = {
#define _(sym, str) str,
foreach_ck_sample_error
#undef _
};
extern vlib_node_registration_t ck_sample_node;
static u8 *
format_ck_sample_trace (u8 * s, va_list * args)
{
s = format(s, "To Do!\n");
return s;
}
/*
vlib_main_t *vm: VPP 的主运行时上下文。
vlib_node_runtime_t *node: 当前节点的运行时数据。
vlib_frame_t * frame: 包含要处理的数据包帧。
*/
static uword ck_sample_node_fn(vlib_main_t *vm, vlib_node_runtime_t *node,
vlib_frame_t * frame)
{
u32 n_left_from, *from, *to_next;
ck_sample_next_t next_index;
//返回一个指向帧中数据包索引数组的指针
//一个帧包含了一组数据包索引,这些索引指向具体的数据包缓冲区。
//在节点的处理函数中,通常需要遍历这些数据包索引并对每个数据包进行处理。
from = vlib_frame_vector_args(frame);
//帧中有效数据包索引的数量,用于后续的遍历操作
n_left_from = frame->n_vectors;
//在 VPP 中,节点处理数据包时,会决定每个数据包的下一个处理节点(即数据包的“下一跳”)。
//为了提高效率,VPP 会缓存最近一次处理的“下一跳”节点的索引,这个索引就存储在 cached_next_index 字段中。
next_index = node->cached_next_index;
while(n_left_from > 0){
u32 n_left_to_next;
//为当前节点的处理函数提供指向下一个节点的帧的访问权
vlib_get_next_frame(vm, node, next_index, to_next, n_left_to_next);
while(n_left_from > 0 && n_left_to_next > 0){
vlib_buffer_t *b0;
u32 bi0, next0 = 0;
bi0 = to_next[0] = from[0];
from += 1;
to_next += 1;
n_left_to_next -= 1;
n_left_from -= 1;
//用于根据数据包的缓冲区索引(buffer index)获取相应的数据包缓冲区(buffer)
b0 = vlib_get_buffer(vm, bi0);
/*
用于获取当前数据包在缓冲区中的起始位置(即指向数据包内容的指针)。
在 VPP 中,数据包缓冲区(vlib_buffer_t)不仅仅存储了数据包本身,
还可能包含一些额外的元数据。因此,vlib_buffer_get_current 提供了一种便捷的方法
来直接获取数据包的起始位置。
*/
void *en0 = vlib_buffer_get_current(b0);
int i = 0;
for (i = 0; i < 34; i++)
{
printf("%02x ", *(u8*)(en0+i));
}
printf("\n");
//一个数据包缓冲区的索引验证并加入到下一个处理节点的帧(frame)中
//是用于逐个将数据包添加到下一个节点的帧中,并减少帧的可用空间。
vlib_validate_buffer_enqueue_x1(vm, node, next_index,
to_next, n_left_to_next, bi0, next0);
}
//将当前节点处理完的数据包帧提交给下一个节点,是用于在节点处理完成后,提交当前帧,并更新其状态。
vlib_put_next_frame(vm, node, next_index, n_left_to_next);
}
return frame->n_vectors;
}
VLIB_REGISTER_NODE (ck_sample_node) = {
.name = "ck_sample", //节点名称
.function = ck_sample_node_fn, //节点的处理函数
.vector_size = sizeof(u32), //每个向量(即每个数据包索引)所占用的内存大小,这里是 sizeof(u32)。
.format_trace = format_ck_sample_trace, //用于格式化节点 trace 信息的函数
.type = VLIB_NODE_TYPE_INTERNAL, //节点类型,这里是内部节点 VLIB_NODE_TYPE_INTERNAL。
.n_errors = ARRAY_LEN(ck_sample_error_strings), //节点可能的错误类型数量
.error_strings = ck_sample_error_strings, //错误类型对应的错误字符串
.n_next_nodes = CK_SAMPLE_NEXT_N, //下一个节点的数量,这里是 CK_SAMPLE_NEXT_N
.next_nodes = { //下一个节点的映射
[CK_SAMPLE_NEXT_IP4] = "ip4-lookup",
[CK_SAMPLE_DROP] = "error-drop",
},
};
src/plugins/下新增pktdump.am
# Copyright (c) 2016 Cisco and/or its affiliates.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
vppplugins_LTLIBRARIES += pktdump_plugin.la
pktdump_plugin_la_SOURCES = \
pktdump/pktdump.c \
pktdump/pktdump_node.c
nobase_apiinclude_HEADERS += \
pktdump/pktdump.h
修改src/plugins/Makefile.am
修改src/configure.ac
编译运行
1、make wise
2、make build
3、make run