VPP-编写一个简单插件

目录

插件运行

插件初始化

1、VLIB_INIT_FUNCTION(ck_sample_init)

2、ck_sample_init

插件注册

feature初始化

命令初始化

注册节点

代码


插件运行

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_registrationvlib_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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值