赵占旭的博客

Cisco VPP插件开发

在VPP中,插件可以在程序启动的时候加载,一般我们会往里面加入node,实现一些功能。

首先介绍怎么直接重定义硬件接口RX到我们的node。

1
2
vnet_hw_interface_rx_redirect_to_node(vnet_main, hw_if_index,
my_graph_node.index /* redirect to my_graph_node */);

这个接口主要是将指定的接口重定向到我们自己实现的node上。
具体的实现我们可以看sample-plugin,或者看以下简单的例子:
这是注册node的代码,里面主要是function、name和next_nodes。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//注册node
VLIB_REGISTER_NODE(my_node) = {
.function = my_node_fn, //node功能函数
.name = "zzx-test", //node名字,唯一
.vector_size = 4,
.format_trace = format_my_trace, //show trace显示
.type = VLIB_NODE_TYPE_INTERNAL, //node类型

.n_errors = ARRAY_LEN(my_error_strings),
.error_strings = my_error_strings,

.n_next_nodes = MY_N_NEXT,

.next_nodes = {
[MY_ETHERNET_INPUT] = "ethernet-input", //下一级node名字
},
};

node功能如下,这里是拿到数据包,执行功能,设置下一级的node三个主要功能:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
static uint64_t my_node_fn(vlib_main_t *vm,
vlib_node_runtime_t *node, vlib_frame_t *frame)
{
uint32_t *from = vlib_frame_vector_args(frame);
uint32_t n_left_from = frame->n_vectors;
my_next_t next_index = node->cached_next_index;
uint32_t pkts_showed = 0;

while(n_left_from > 0) {
uint32_t *to_next;
uint32_t n_left_to_next;
vlib_get_next_frame(vm, node, next_index, to_next, n_left_to_next);

/*
* n_left_from表示vector拿到多少个数据包
* n_left_to_next表示上次缓存的下一级node剩下的vector位置
* 所以循环表示的是当前node还能拿到多少个包,
* 下一级能接收多少包,以少的为准。有0的话就停止执行
*/
while(n_left_from > 0 && n_left_to_next > 0) {
uint32_t next0 = MY_ETHERNET_INPUT;
uint32_t bi0 = from[0];
to_next[0] = bi0;
from += 1;
to_next += 1;
n_left_from -= 1;
n_left_to_next -= 1;

//拿到数据包
vlib_buffer_t *b0 = vlib_get_buffer(vm, bi0);

//当前node的主要功能是输出数据包内容到终端
void *en0 = vlib_buffer_get_current(b0);
show_packet(en0);

//将数据包入队给下一级node
pkts_showed += 1;
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);
}

//增加计数器,主要是加在show err命令了,vpp本身的node也是加在那
vlib_node_increment_counter(vm, my_node.index, MY_ERROR_SHOW, pkts_showed);
return frame->n_vectors;
}

以上是node.c的基本展示。

下面介绍下插件注册时需要的一些函数。
首先是注册初始化函数。

1
2
3
4
5
6
7
8
//我们没有什么特别的功能,所以初始化没有做任何事情
static clib_error_t *my_init(vlib_main_t *vm)
{
return 0;
}

//此处是我们将初始化函数加载到vpp里面,以供程序运行的时候初始化
VLIB_INIT_FUNCTION(my_init);

接下来是必备函数,vpp在加载插件会调用这个函数,传递给我们一些必须的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//vm里面记录了我们需要的大部分东西,好多接口里面都会调用
//vnet_main是我们连接其他node或者网络相关的数据
//这里因为运行的比较早,DPDK硬件初始化还没开始。
//所以我们在连接硬件接口RX的时候,这里不能做,这里最好就是获取一些基本的数据
clib_error_t *vlib_plugin_register(vlib_main_t *vm, vnet_plugin_handoff_t *h,
int32_t from_early_init)
{
my_main_t *mm = &my_main;

mm->vlib_main = vm;
mm->vnet_main = h->vnet_main;
mm->ethernet_main = h->ethernet_main;

return NULL;
}

因为如上说到硬件还没初始化,所以我们不能在这里直接去重定向硬件RX,但是其他地方也没有合适的接口被调用,所以我们考虑采用命令行去配置需要重定向的接口,命令行的支持如下:

1
2
3
4
5
6
//命令行注册,path表示的是命令,function是执行函数
VLIB_CLI_COMMAND(show_packet_command, static) = {
.path = "show packet",
.short_help = "show packet <interface name> [disable]",
.function = my_command_enable_disable_fn,
};
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
34
35
36
37
38
39
40
41
static clib_error_t *my_command_enable_disable_fn(vlib_main_t *vm,
unformat_input_t *input, vlib_cli_command_t *cmd)
{
my_main_t *mm = &my_main;
int32_t enable_disable = 1;
uint32_t sw_if_index = UINT32_MAX;

while(unformat_check_input(input) != UNFORMAT_END_OF_INPUT) {
//判定是开还是关
if(unformat(input, "disable"))
enable_disable = 0;
//获取需要重定向的RX
else if(unformat(input, "%U", unformat_vnet_sw_interface,
mm->vnet_main, &sw_if_index))
;
else
break;
}

if(sw_if_index == UINT32_MAX)
return clib_error_return(0, "Please specify an interface...");

int32_t rv = my_show_packet_enable_disable(mm, sw_if_index, enable_disable);

switch(rv) {
case 0:
break;

case VNET_API_ERROR_INVALID_SW_IF_INDEX:
return clib_error_return(0, "Invalid interface, only works on physical ports");
break;

case VNET_API_ERROR_UNIMPLEMENTED:
return clib_error_return(0, "Device driver doesn't support redirection");
break;

default:
return clib_error_return(0, "%s returned %d", __FUNCTION__, rv);
}
return NULL;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int32_t my_show_packet_enable_disable(my_main_t *mm,
uint32_t sw_if_index, int32_t enable_disable)
{
//释放sw index
if(pool_is_free_index(mm->vnet_main->interface_main.sw_interfaces, sw_if_index))
return VNET_API_ERROR_INVALID_SW_IF_INDEX;

//获取RX的结构
vnet_sw_interface_t *sw = vnet_get_sw_interface(mm->vnet_main, sw_if_index);
if(sw->type != VNET_SW_INTERFACE_TYPE_HARDWARE) {
return VNET_API_ERROR_INVALID_SW_IF_INDEX;
}

//node_index是my_node.index时是重定向到我的node,否则~0表示关闭该RX的重定向
uint32_t node_index = enable_disable ? my_node.index : UINT32_MAX;
int32_t rv = vnet_hw_interface_rx_redirect_to_node(mm->vnet_main,
sw_if_index, node_index);
return rv;
}

以上是直接重定向RX的操作。

另外还有需要插入node连接其他node时候,VPP提供了几个常用接口:

1
2
3
ethernet_register_input_type (vm, ETHERNET_TYPE_ARP, arp_input_node.index);
//这个是ARP从ethernet-input node注册ARP协议的下一级node,
//还有ipv4等等也是这样注册,我们可以注册其他的协议
1
2
3
ip4_register_protocol (IP_PROTOCOL_UDP, udp4_input_node.index);
//这个是从ip-local node注册4层协议的下一级node,其他协议比如tcp也可以这样注册。
ip6_register_protocol (IP_PROTOCOL_L2TP, l2t_decap_node.index);
1
2
udp_register_dst_port (vm, UDP_DST_PORT_vxlan,
vxlan_input_node.index, 1 /* is_ip4 */);

注意:所有文章非特别说明皆为原创。为保证信息与源同步,转载时请务必注明文章出处!谢谢合作 :-)

原始链接:http://zhaozhanxu.com/2016/04/27/VPP/2016-04-27-VPP-Plugin/

许可协议: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。