本专栏知识点是通过零声教育的线上课学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接https://xxetb.xetslk.com/s/12PH1r C/C++后台高级服务器课程介绍 详细查看课程的服务
flowtable的plugin的运行流程
plugin的基本流程
flowtable plugin代码实现
flowtable plugin的初始化
注册节点
在vpp终端输入:show vlib graph
flowtable的get flowinfo的实现
//当node执行到这个回调函数的时候,vpp数据其实已经在内存中了,这个数据是在vlib_buffer_t中存储的
//参数1--vm:当前处理node的链表头;参数3--frame:内存中要处理的数据
static uword flowtable_getinfo (struct vlib_main_t * vm,
struct vlib_node_runtime_t * node, struct vlib_frame_t * frame) {
u32 n_left_from, *from, *to_next;
u32 next_index = node->cached_next_index;
from = vlib_frame_vector_args(frame);//from指针指向的内存中可能会含有多个mbuf(vpp数据)
//一个vector就是一帧数据,参数frame是一个数组,数组的每一个元素就代表一帧数据
n_left_from = frame->n_vectors;
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;
b0 = vlib_get_buffer(vm, bi0);
ip4_header_t *ip0 = vlib_buffer_get_current(b0);
ip4_address_t ip_src = ip0->src_address;
ip4_address_t ip_dst = ip0->dst_address;
//数据报文中包含的ip地址和端口号信息只需要解析一次
u32 sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];
struct in_addr addr;
addr.s_addr = ip_src.as_u32;
printf("sw_if_index0: %d, ip_src: %s ", sw_if_index0, inet_ntoa(addr));
addr.s_addr = ip_dst.as_u32;
printf(" ip_dst: %s \n", inet_ntoa(addr));
/*
if (ip_src.as_u32 % 2) {
next0 = FT_NEXT_LOAD_BALANCER;
}
*/
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; //根据这个返回值,决定了下一个要跳转的node
}
static char *flowtable_error_strings[] = {
#define _(sym,string) string,
foreach_flowtable_error
#undef _
};
VLIB_REGISTER_NODE(flowtable_node) = {
.function = flowtable_getinfo,//执行node时候的回调函数
.name = "flow-table",
.vector_size = sizeof(u32),
.format_trace = format_flowtable_getinfo,//调试追踪
.type = VLIB_NODE_TYPE_INTERNAL,//当前节点是个中间节点
.n_errors = FLOWTABLE_N_ERROR,
.error_strings = flowtable_error_strings,
.n_next_nodes = FT_NEXT_N_NEXT,
.next_nodes = { //node的名字如ip4-lookup等都是固定的
[FT_NEXT_IP4] = "ip4-lookup",
[FT_NEXT_DROP] = "error-drop",//返回值error决定了node的执行流
[FT_NEXT_ETHERNET_INPUT] = "ethernet-input",
[FT_NEXT_LOAD_BALANCER] = "load-balance",
[FT_NEXT_INTERFACE_OUTPUT] = "interface-output",
}
};
vlib_buffer_t结构体
/* VLIB buffer representation. */
typedef struct
{
CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
STRUCT_MARK (template_start);
/* Offset within data[] that we are currently processing.
If negative current header points into predata area. */
i16 current_data; /**< signed offset in data[], pre_data[]
that we are currently processing.
If negative current header points into predata area.
*/
u16 current_length; /**< Nbytes between current data and
the end of this buffer.
*/
u32 flags; /**< buffer flags:
<br> VLIB_BUFFER_FREE_LIST_INDEX_MASK: bits used to store free list index,
<br> VLIB_BUFFER_IS_TRACED: trace this buffer.
<br> VLIB_BUFFER_NEXT_PRESENT: this is a multi-chunk buffer.
<br> VLIB_BUFFER_TOTAL_LENGTH_VALID: as it says
<br> VLIB_BUFFER_REPL_FAIL: packet replication failure
<br> VLIB_BUFFER_RECYCLE: as it says
<br> VLIB_BUFFER_FLOW_REPORT: buffer is a flow report,
<br> VLIB_BUFFER_EXT_HDR_VALID: buffer contains valid external buffer manager header,
set to avoid adding it to a flow report
<br> VLIB_BUFFER_FLAG_USER(n): user-defined bit N
*/
/* any change to the following line requres update of
* vlib_buffer_get_free_list_index(...) and
* vlib_buffer_set_free_list_index(...) functions */
#define VLIB_BUFFER_FREE_LIST_INDEX_MASK ((1 << 5) - 1)
#define VLIB_BUFFER_IS_TRACED (1 << 5)
#define VLIB_BUFFER_LOG2_NEXT_PRESENT (6)
#define VLIB_BUFFER_NEXT_PRESENT (1 << VLIB_BUFFER_LOG2_NEXT_PRESENT)
#define VLIB_BUFFER_IS_RECYCLED (1 << 7)
#define VLIB_BUFFER_TOTAL_LENGTH_VALID (1 << 8)
#define VLIB_BUFFER_REPL_FAIL (1 << 9)
#define VLIB_BUFFER_RECYCLE (1 << 10)
#define VLIB_BUFFER_FLOW_REPORT (1 << 11)
#define VLIB_BUFFER_EXT_HDR_VALID (1 << 12)
/* User defined buffer flags. */
#define LOG2_VLIB_BUFFER_FLAG_USER(n) (32 - (n))
#define VLIB_BUFFER_FLAG_USER(n) (1 << LOG2_VLIB_BUFFER_FLAG_USER(n))
STRUCT_MARK (template_end);
u32 next_buffer; /**< Next buffer for this linked-list of buffers.
Only valid if VLIB_BUFFER_NEXT_PRESENT flag is set.
*/
vlib_error_t error; /**< Error code for buffers to be enqueued
to error handler.
*/
u32 current_config_index; /**< Used by feature subgraph arcs to
visit enabled feature nodes
*/
u8 feature_arc_index; /**< Used to identify feature arcs by intermediate
feature node
*/
u8 n_add_refs; /**< Number of additional references to this buffer. */
u8 buffer_pool_index; /**< index of buffer pool this buffer belongs. */
u8 dont_waste_me[1]; /**< Available space in the (precious)
first 32 octets of buffer metadata
Before allocating any of it, discussion required!
*/
u32 opaque[10]; /**< Opaque data used by sub-graphs for their own purposes.
See .../vnet/vnet/buffer.h
*/
CLIB_CACHE_LINE_ALIGN_MARK (cacheline1);
u32 trace_index; /**< Specifies index into trace buffer
if VLIB_PACKET_IS_TRACED flag is set.
*/
u32 recycle_count; /**< Used by L2 path recycle code */
u32 total_length_not_including_first_buffer;
/**< Only valid for first buffer in chain. Current length plus
total length given here give total number of bytes in buffer chain.
*/
u32 align_pad; /**< available */
u32 opaque2[12]; /**< More opaque data, see ../vnet/vnet/buffer.h */
/***** end of second cache line */
CLIB_CACHE_LINE_ALIGN_MARK (cacheline2);
u8 pre_data[VLIB_BUFFER_PRE_DATA_SIZE]; /**< Space for inserting data
before buffer start.
Packet rewrite string will be
rewritten backwards and may extend
back before buffer->data[0].
Must come directly before packet data.
*/
u8 data[0]; /**< Packet data. Hardware DMA here */
} vlib_buffer_t; /* Must be a multiple of 64B. */
vlib_frame_t与mbuf的关系
flowtable的loadbalancer的框架
命令的初始化
node handler的实现
命令的注册
节点回调函数的实现
//node handler
static uword load_balance (vlib_main_t *vm,
vlib_node_runtime_t *node, vlib_frame_t *frame) {
//vlib_frame_vector_args从上一帧frame到下一帧frame的参数都有哪些
u32 *to_next, *from = vlib_frame_vector_args(frame);
u32 n_left_from = frame->n_vectors;
u32 next_index = node->cached_next_index;
u32 pkts_processed = 0;
loadbalancer_runtime_t *rt = vlib_node_get_runtime_data(vm, loadbalancer_node.index);
while (n_left_from > 0) {
u32 pi0;
u32 next0;
u32 n_left_to_next;
//debug("debug:[%s:%s:%d] next_index:%d, n_left_to_next: %d\n", __FILE__, __func__, __LINE__, next_index, n_left_to_next);
vlib_get_next_frame(vm, node, next_index, to_next, n_left_to_next);
//debug("debug:[%s:%s:%d] next_index:%d, n_left_to_next: %d\n", __FILE__, __func__, __LINE__, next_index, n_left_to_next);
while (n_left_from > 0 && n_left_to_next > 0) {
next0 = LB_NEXT_INTERFACE_OUTPUT;
pi0 = to_next[0] = from[0];
vlib_buffer_t *b0 = vlib_get_buffer(vm, pi0);
pkts_processed ++;
flow_info_t *fi = *(flow_info_t**)b0->opaque;
u32 sw_if_index0 = fi->lb.sw_if_index_current;
fi->offloaded = 1;
fi->cached_next_node = FT_NEXT_INTERFACE_OUTPUT;
fi->lb.sw_if_index_rev = rt->sw_if_index_source;
rt->last_target_index ++;
if (rt->last_target_index >= vec_len(rt->sw_if_target)) {
rt->last_target_index = 0;
}
fi->lb.sw_if_index_dst = rt->sw_if_target[rt->last_target_index];
vnet_buffer(b0)->sw_if_index[VLIB_RX] = sw_if_index0;
if (sw_if_index0 == rt->sw_if_index_source) {
vnet_buffer(b0)->sw_if_index[VLIB_TX] = fi->lb.sw_if_index_rev;
} else {
vnet_buffer(b0)->sw_if_index[VLIB_TX] = fi->lb.sw_if_index_dst;
}
from ++;
to_next ++;
n_left_from --;
n_left_to_next --;
if (b0->flags & VLIB_BUFFER_IS_TRACED) {
lb_trace_t *t = vlib_add_trace(vm, node, b0, sizeof(*t));
t->sw_if_in = sw_if_index0;
t->next_index = next0;
t->sw_if_out = vnet_buffer(b0)->sw_if_index[VLIB_TX];
}
vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next, n_left_to_next, pi0, next0);
}
//处理完buffer中的数据后,一定要将数据set回buffer中去,这样下一个节点运行的时候也就可以使用这个数据
vlib_put_next_frame(vm, node, next_index, n_left_to_next);
}
vlib_node_increment_counter(vm, loadbalancer_node.index, LOADBALANCER_ERROR_PROCESSED, pkts_processed);
return frame->n_vectors;
}
static char *loadbalancer_error_strings[] = {
#define _(sym,string) string,
foreach_loadbalancer_error
#undef _
};
命令的设置
int loadbalancer_set_targets(loadbalancer_main_t *lb, u32 sw_if_index_source,
u32 *sw_if_index_targets) {
u32 *sw_if_index;
debug("debug:[%s:%s:%d] loadbalancer_node.index:%d\n", __FILE__, __func__, __LINE__, loadbalancer_node.index);
loadbalancer_runtime_t *rt = vlib_node_get_runtime_data(lb->vlib_main, loadbalancer_node.index);
debug("debug:[%s:%s:%d] sw_if_index_source:%d\n", __FILE__, __func__, __LINE__, sw_if_index_source);
rt->sw_if_index_source = sw_if_index_source;
vec_foreach(sw_if_index, sw_if_index_targets) {
vec_add1(rt->sw_if_target, *sw_if_index);//往vec向量中增加一个节点
}
return 0;
}
static clib_error_t *set_lb_target_command_fn (vlib_main_t * vm,
unformat_input_t * input,
vlib_cli_command_t * cmd) {
flowtable_main_t *fm = &flowtable_main;
loadbalancer_main_t *lb = &loadbalancer_main;
u32 sw_if_index_source = ~0;
u32 sw_if_index = ~0;//vpp命令show int中显示的网口的Idx
u32 *sw_if_index_targets = NULL;
int source = 1;
vec_alloc(sw_if_index_targets, 10);
//vec_validate(sw_if_index_targets, 10);
while (unformat_check_input(input) != UNFORMAT_END_OF_INPUT) {
if (unformat(input, "to")) {
source = 0;
} else if (unformat(input, "%U", unformat_vnet_sw_interface,
fm->vnet_main, &sw_if_index)) {
debug("debug:[%s:%s:%d] sw_if_index:%d\n", __FILE__, __func__, __LINE__, sw_if_index);
if (source) {
sw_if_index_source = sw_if_index;
} else {
//将网卡GigabitEthernetb/0/0 GigabitEthernet13/0/0对应的Idx添加到GigabitEthernet3/0/0
vec_add1(sw_if_index_targets, sw_if_index);
}
} else break;
}
if (sw_if_index == ~0) {
return clib_error_return(0, "No Source Interface specified");
}
if (vec_len(sw_if_index_targets) <= 0) {
return clib_error_return(0, "No Target Interface specified");
}
int rv = flowtable_enable(fm, sw_if_index_source, 1);
u32 *v;
vec_foreach(v, sw_if_index_targets) {
rv = flowtable_enable(fm, *v, 1);
}
debug("debug:[%s:%s:%d] rv:%d\n", __FILE__, __func__, __LINE__, rv);
switch(rv) {
case 0: break;
case VNET_API_ERROR_INVALID_SW_IF_INDEX: return clib_error_return(0, "Invalid interface");
case VNET_API_ERROR_UNIMPLEMENTED: return clib_error_return(0, "Device driver doesn't support redirection"); break;
default : return clib_error_return(0, "flowtable_enable return %d", rv); break;
}
rv = loadbalancer_set_targets(lb, sw_if_index_source, sw_if_index_targets);
if (rv) {
return clib_error_return(0, "set interface loadbalancer return %d", rv);
}
return 0;
}
//命令的设置
//set interface loadbalanced GigabitEthernet3/0/0 to GigabitEthernetb/0/0 GigabitEthernet13/0/0
VLIB_CLI_COMMAND(set_interface_loadbalanced_command) = {
.path = "set interface loadbalanced",
.short_help = "set interface loadbalanced <interface> to <interfaces-list>",
.function = set_lb_target_command_fn,
};
flowtable流程分析
从flowtable节点到loadbalancer节点的跳转
根据flowtable_getinfo回调函数的返回值,来决定跳转到loadbalancer节点中去:
节点的运行时参数的设置
- 在一个节点中通过调用vlib_node_get_runtime_data设置运行时参数,在另一个节点中调用vlib_node_get_runtime_data就可以获取到其他节点设置的运行时参数
- 在loadbalancer节点初始化的时候设置运行时参数
- 在loadbalancer设置命令的时候设置运行时的参数:
- 在flowtable的节点中也可以获取到在loadbalancer节点中设置的运行时参数
flowtable完整代码
vppplugins_LTLIBRARIES += flowtable_plugin.la
flowtable_plugin_la_SOURCES = \
flowtable/flowtable.c \
flowtable/flowtable_node.c \
flowtable/loadbalancer.c \
flowtable/loadbalancer_node.c
nobase_apiinclude_HEADERS += \
flowtable/flowtable.h \
flowtable/loadbalancer.h
#include <vppinfra/error.h>
#include <vnet/vnet.h>
#include <vnet/ip/ip.h>
#include <vppinfra/pool.h>
#include <vppinfra/bihash_8_8.h>
#define foreach_flowtable_error \
_(THRU, "Flowtable packets gone thru") \
_(FLOW_CREATE, "Flowtable packets which created a new flow") \
_(FLOW_HIT, "Flowtable packets with an existing flow")
typedef enum {
#define _(sym,str) FLOWTABLE_ERROR_##sym,
foreach_flowtable_error
#undef _
FLOWTABLE_N_ERROR
} flowtable_error_t;
typedef enum {
FT_NEXT_IP4,
FT_NEXT_DROP,
FT_NEXT_ETHERNET_INPUT,
FT_NEXT_LOAD_BALANCER,
FT_NEXT_INTERFACE_OUTPUT,
FT_NEXT_N_NEXT
} flowtable_next_t;
typedef struct {
u64 hash;
u32 cached_next_node;
u16 offloaded;
u16 sig_len;
union {
struct {
ip6_address_t src, dst;
u8 proto;
u16 src_port, dst_port;
} ip6;
struct {
ip4_address_t src, dst;
u8 proto;
u16 src_port, dst_port;
} ip4;
u8 data[32];
} signature;
u64 last_ts;
struct {
u32 straight;
u32 reverse;
} packet_stats;
union {
struct {
u32 SYN:1;
u32 SYN_ACK:1;
u32 SYN_ACK_ACK:1;
u32 FIN:1;
u32 FIN_ACK:1;
u32 FIN_ACK_ACK:1;
u32 last_seq_number, last_ack_number;
} tcp;
struct {
uword flow_info_ptr;
u32 sw_if_index_dst;
u32 sw_if_index_rev;
u32 sw_if_index_current;
} lb;
u8 flow_data[CLIB_CACHE_LINE_BYTES];
} ;
} flow_info_t;
typedef struct {
flow_info_t *flows;
BVT(clib_bihash) flows_ht;
vlib_main_t *vlib_main;
vnet_main_t *vnet_main;
u32 ethernet_input_next_index;
} flowtable_main_t;
#define FM_NUM_BUCKETS 4
#define FM_MEMORY_SIZE (256<<16)
flowtable_main_t flowtable_main;
extern vlib_node_registration_t flowtable_node;
int flowtable_enable(flowtable_main_t *fm, u32 sw_if_index, int enable);
#include "flowtable.h"
#include <vnet/plugin/plugin.h>
#include <arpa/inet.h>
vlib_node_registration_t flowtable_node;
typedef struct {
u64 hash;
u32 sw_if_index;
u32 next_index;
u32 offloaded;
} flow_trace_t;
static u8 *format_flowtable_getinfo (u8 * s, va_list * args) {
CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
flow_trace_t *t = va_arg(*args, flow_trace_t*);
s = format(s, "FlowInfo - sw_if_index %d, hash = 0x%x, next_index = %d, offload = %d",
t->sw_if_index,
t->hash, t->next_index, t->offloaded);
return s;
}
u64 hash4(ip4_address_t ip_src, ip4_address_t ip_dst, u8 protocol, u16 port_src, u16 port_dst) {
return ip_src.as_u32 ^ ip_dst.as_u32 ^ protocol ^ port_src ^ port_dst;
}
//当node执行到这个回调函数的时候,vpp数据其实已经在内存中了,这个数据是在vlib_buffer_t中存储的
//参数1--vm:当前处理node的链表头;参数3--frame:内存中要处理的数据
static uword flowtable_getinfo (struct vlib_main_t * vm,
struct vlib_node_runtime_t * node, struct vlib_frame_t * frame) {
u32 n_left_from, *from, *to_next;
u32 next_index = node->cached_next_index;
from = vlib_frame_vector_args(frame);//from指针指向的内存中可能会含有多个mbuf(vpp数据)
//一个vector就是一帧数据,参数frame是一个数组,数组的每一个元素就代表一帧数据
n_left_from = frame->n_vectors;
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;
b0 = vlib_get_buffer(vm, bi0);
ip4_header_t *ip0 = vlib_buffer_get_current(b0);
ip4_address_t ip_src = ip0->src_address;
ip4_address_t ip_dst = ip0->dst_address;
//数据报文中包含的ip地址和端口号信息只需要解析一次
u32 sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];
struct in_addr addr;
addr.s_addr = ip_src.as_u32;
printf("sw_if_index0: %d, ip_src: %s ", sw_if_index0, inet_ntoa(addr));
addr.s_addr = ip_dst.as_u32;
printf(" ip_dst: %s \n", inet_ntoa(addr));
/*
loadbalancer_runtime_t *rt = vlib_node_get_runtime_data(vm, loadbalancer_node.index);
int target_count = vec_len(rt->sw_if_target);
if (ip_src.as_u32 % 2) {
next0 = FT_NEXT_LOAD_BALANCER;
}
*/
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; //根据这个返回值,决定了下一个要跳转的node
}
static char *flowtable_error_strings[] = {
#define _(sym,string) string,
foreach_flowtable_error
#undef _
};
VLIB_REGISTER_NODE(flowtable_node) = {
.function = flowtable_getinfo,//执行node时候的回调函数
.name = "flow-table",
.vector_size = sizeof(u32),
.format_trace = format_flowtable_getinfo,//调试追踪
.type = VLIB_NODE_TYPE_INTERNAL,//当前节点是个中间节点
.n_errors = FLOWTABLE_N_ERROR,
.error_strings = flowtable_error_strings,
.n_next_nodes = FT_NEXT_N_NEXT,
.next_nodes = { //node的名字如ip4-lookup等都是固定的
[FT_NEXT_IP4] = "ip4-lookup",
[FT_NEXT_DROP] = "error-drop",//返回值error决定了node的执行流
[FT_NEXT_ETHERNET_INPUT] = "ethernet-input",
[FT_NEXT_LOAD_BALANCER] = "load-balance",
[FT_NEXT_INTERFACE_OUTPUT] = "interface-output",
}
};
VLIB_PLUGIN_REGISTER() = {
.version = "1.0",
.description = "sample of flowtable",
};
//flowtable插件加载到vpp框架中的时候,会调用flowtable_init这个函数
static clib_error_t *flowtable_init(vlib_main_t *vm) {
clib_error_t *error = 0;
flowtable_main_t *fm = &flowtable_main;
fm->vnet_main = vnet_get_main();
fm->vlib_main = vm;
flow_info_t *flow;
pool_get_aligned(fm->flows, flow, CLIB_CACHE_LINE_BYTES);
pool_put(fm->flows, flow);
BV(clib_bihash_init)(&fm->flows_ht, "flow hash table", FM_NUM_BUCKETS, FM_MEMORY_SIZE);
return error;
}
//每一个node的初始化函数
VLIB_INIT_FUNCTION(flowtable_init);
#include "flowtable.h"
#include <vnet/plugin/plugin.h>
int flowtable_enable(flowtable_main_t *fm, u32 sw_if_index, int enable) {
u32 node_index = enable ? flowtable_node.index : ~0;
printf("debug:[%s:%s:%d] node_index:%d\n", __FILE__, __func__, __LINE__, flowtable_node.index);
return vnet_hw_interface_rx_redirect_to_node(fm->vnet_main, sw_if_index, node_index);
}
static clib_error_t *flowtable_command_enable_fn (struct vlib_main_t * vm,
unformat_input_t * input, struct vlib_cli_command_t * cmd) {
flowtable_main_t *fm = &flowtable_main;
u32 sw_if_index = ~0;
int enable_disable = 1;
while (unformat_check_input(input) != UNFORMAT_END_OF_INPUT) {
if (unformat(input, "disable")) {
enable_disable = 0;
} else if (unformat(input, "%U", unformat_vnet_sw_interface, fm->vnet_main, &sw_if_index))
;
else break;
}
if (sw_if_index == ~0) {
return clib_error_return(0, "No Interface specified");
}
int rv = flowtable_enable(fm, sw_if_index, enable_disable);
if (rv) {
if (rv == VNET_API_ERROR_INVALID_SW_IF_INDEX) {
return clib_error_return(0, "Invalid interface");
} else if (rv == VNET_API_ERROR_UNIMPLEMENTED) {
return clib_error_return (0, "Device driver doesn't support redirection");
} else {
return clib_error_return (0, "flowtable_enable_disable returned %d", rv);
}
}
return 0;
}
VLIB_CLI_COMMAND(flowtable_interface_enable_disable_command) = {
.path = "flowtable",
.short_help = "flowtable <interface> [disable]",
.function = flowtable_command_enable_fn,
};
#include "flowtable.h"
#include "loadbalancer.h"
#include <vnet/plugin/plugin.h>
int loadbalancer_set_targets(loadbalancer_main_t *lb, u32 sw_if_index_source,
u32 *sw_if_index_targets) {
u32 *sw_if_index;
debug("debug:[%s:%s:%d] loadbalancer_node.index:%d\n", __FILE__, __func__, __LINE__, loadbalancer_node.index);
//获取到的rt就是运行时的参数
loadbalancer_runtime_t *rt = vlib_node_get_runtime_data(lb->vlib_main, loadbalancer_node.index);
debug("debug:[%s:%s:%d] sw_if_index_source:%d\n", __FILE__, __func__, __LINE__, sw_if_index_source);
rt->sw_if_index_source = sw_if_index_source;
vec_foreach(sw_if_index, sw_if_index_targets) {
vec_add1(rt->sw_if_target, *sw_if_index);//往vec向量中增加一个节点
}
return 0;
}
static clib_error_t *set_lb_target_command_fn (vlib_main_t * vm,
unformat_input_t * input,
vlib_cli_command_t * cmd) {
flowtable_main_t *fm = &flowtable_main;
loadbalancer_main_t *lb = &loadbalancer_main;
u32 sw_if_index_source = ~0;
u32 sw_if_index = ~0;//vpp命令show int中显示的网口的Idx
u32 *sw_if_index_targets = NULL;
int source = 1;
vec_alloc(sw_if_index_targets, 10);
//vec_validate(sw_if_index_targets, 10);
while (unformat_check_input(input) != UNFORMAT_END_OF_INPUT) {
if (unformat(input, "to")) {
source = 0;
} else if (unformat(input, "%U", unformat_vnet_sw_interface,
fm->vnet_main, &sw_if_index)) {
debug("debug:[%s:%s:%d] sw_if_index:%d\n", __FILE__, __func__, __LINE__, sw_if_index);
if (source) {
sw_if_index_source = sw_if_index;
} else {
//将网卡GigabitEthernetb/0/0 GigabitEthernet13/0/0对应的Idx添加到GigabitEthernet3/0/0
vec_add1(sw_if_index_targets, sw_if_index);
}
} else break;
}
if (sw_if_index == ~0) {
return clib_error_return(0, "No Source Interface specified");
}
if (vec_len(sw_if_index_targets) <= 0) {
return clib_error_return(0, "No Target Interface specified");
}
int rv = flowtable_enable(fm, sw_if_index_source, 1);
u32 *v;
vec_foreach(v, sw_if_index_targets) {
rv = flowtable_enable(fm, *v, 1);
}
debug("debug:[%s:%s:%d] rv:%d\n", __FILE__, __func__, __LINE__, rv);
switch(rv) {
case 0: break;
case VNET_API_ERROR_INVALID_SW_IF_INDEX: return clib_error_return(0, "Invalid interface");
case VNET_API_ERROR_UNIMPLEMENTED: return clib_error_return(0, "Device driver doesn't support redirection"); break;
default : return clib_error_return(0, "flowtable_enable return %d", rv); break;
}
rv = loadbalancer_set_targets(lb, sw_if_index_source, sw_if_index_targets);
if (rv) {
return clib_error_return(0, "set interface loadbalancer return %d", rv);
}
return 0;
}
//命令的设置
//set interface loadbalanced GigabitEthernet3/0/0 to GigabitEthernetb/0/0 GigabitEthernet13/0/0
VLIB_CLI_COMMAND(set_interface_loadbalanced_command) = {
.path = "set interface loadbalanced",
.short_help = "set interface loadbalanced <interface> to <interfaces-list>",
.function = set_lb_target_command_fn,
};
#define debug printf
#define foreach_loadbalancer_error _(PROCESSED, "Loadbalancer packets gone thru")
typedef enum {
#define _(sym,str) LOADBALANCER_ERROR_##sym,
foreach_loadbalancer_error
#undef _
LOADBALANCER_N_ERROR
} loadbalancer_error_t;
typedef struct {
vlib_main_t *vlib_main;
vnet_main_t *vnet_main;
} loadbalancer_main_t;
typedef struct {
loadbalancer_main_t *lbm;
u32 sw_if_index_source;
u32 *sw_if_target;
u32 last_target_index;
} loadbalancer_runtime_t;
loadbalancer_main_t loadbalancer_main;
extern vlib_node_registration_t loadbalancer_node;
#include <vppinfra/types.h>
#include "flowtable.h"
#include "loadbalancer.h"
vlib_node_registration_t loadbalancer_node;
typedef enum {
LB_NEXT_DROP,
LB_NEXT_INTERFACE_OUTPUT,
LB_NEXT_N_NEXT
} loadbalancer_next_t;
typedef struct {
u32 sw_if_in;
u32 sw_if_out;
u32 next_index;
} lb_trace_t;
static u8 * format_loadbalance(u8 *s, va_list *args) {
CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
lb_trace_t *t = va_arg(*args, lb_trace_t*);
s = format(s, "LoadBalance - sw_if_in %d, sw_if_out %d, next_index = %d",
t->sw_if_in, t->sw_if_out, t->next_index);
return s;
}
//node handler
static uword load_balance (vlib_main_t *vm,
vlib_node_runtime_t *node, vlib_frame_t *frame) {
//vlib_frame_vector_args从上一帧frame到下一帧frame的参数都有哪些
u32 *to_next, *from = vlib_frame_vector_args(frame);
u32 n_left_from = frame->n_vectors;
u32 next_index = node->cached_next_index;
u32 pkts_processed = 0;
loadbalancer_runtime_t *rt = vlib_node_get_runtime_data(vm, loadbalancer_node.index);
while (n_left_from > 0) {
u32 pi0;
u32 next0;
u32 n_left_to_next;
//debug("debug:[%s:%s:%d] next_index:%d, n_left_to_next: %d\n", __FILE__, __func__, __LINE__, next_index, n_left_to_next);
vlib_get_next_frame(vm, node, next_index, to_next, n_left_to_next);
//debug("debug:[%s:%s:%d] next_index:%d, n_left_to_next: %d\n", __FILE__, __func__, __LINE__, next_index, n_left_to_next);
while (n_left_from > 0 && n_left_to_next > 0) {
next0 = LB_NEXT_INTERFACE_OUTPUT;
pi0 = to_next[0] = from[0];
vlib_buffer_t *b0 = vlib_get_buffer(vm, pi0);
pkts_processed ++;
flow_info_t *fi = *(flow_info_t**)b0->opaque;
u32 sw_if_index0 = fi->lb.sw_if_index_current;
fi->offloaded = 1;
fi->cached_next_node = FT_NEXT_INTERFACE_OUTPUT;
fi->lb.sw_if_index_rev = rt->sw_if_index_source;
rt->last_target_index ++;
if (rt->last_target_index >= vec_len(rt->sw_if_target)) {
rt->last_target_index = 0;
}
fi->lb.sw_if_index_dst = rt->sw_if_target[rt->last_target_index];
vnet_buffer(b0)->sw_if_index[VLIB_RX] = sw_if_index0;
if (sw_if_index0 == rt->sw_if_index_source) {
vnet_buffer(b0)->sw_if_index[VLIB_TX] = fi->lb.sw_if_index_rev;
} else {
vnet_buffer(b0)->sw_if_index[VLIB_TX] = fi->lb.sw_if_index_dst;
}
from ++;
to_next ++;
n_left_from --;
n_left_to_next --;
if (b0->flags & VLIB_BUFFER_IS_TRACED) {
lb_trace_t *t = vlib_add_trace(vm, node, b0, sizeof(*t));
t->sw_if_in = sw_if_index0;
t->next_index = next0;
t->sw_if_out = vnet_buffer(b0)->sw_if_index[VLIB_TX];
}
vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next, n_left_to_next, pi0, next0);
}
//处理完buffer中的数据后,一定要将数据set回buffer中去,这样下一个节点运行的时候也就可以使用这个数据
vlib_put_next_frame(vm, node, next_index, n_left_to_next);
}
vlib_node_increment_counter(vm, loadbalancer_node.index, LOADBALANCER_ERROR_PROCESSED, pkts_processed);
return frame->n_vectors;
}
static char *loadbalancer_error_strings[] = {
#define _(sym,string) string,
foreach_loadbalancer_error
#undef _
};
VLIB_REGISTER_NODE(loadbalancer_node) = {
.function = load_balance,
.name = "load-balance",
.vector_size = sizeof(u32),
.format_trace = format_loadbalance,
.type = VLIB_NODE_TYPE_INTERNAL,
.runtime_data_bytes = sizeof(loadbalancer_runtime_t),
.n_errors = LOADBALANCER_N_ERROR,
.error_strings = loadbalancer_error_strings,
.n_next_nodes = LB_NEXT_N_NEXT,
.next_nodes = {
[LB_NEXT_DROP] = "error-drop",
[LB_NEXT_INTERFACE_OUTPUT] = "interface-output",
}
};
//命令初始化的时候对命令的参数进行初始化,loadbalancer_init是在VLIB_REGISTER_NODE执行之后执行的
static clib_error_t *loadbalancer_init(vlib_main_t *vm) {
loadbalancer_main_t *lbm = &loadbalancer_main;
//获取每个node的runtime参数
loadbalancer_runtime_t *rt = vlib_node_get_runtime_data(vm, loadbalancer_node.index);
rt->lbm = lbm;
lbm->vlib_main = vm;
lbm->vnet_main = vnet_get_main();
vec_alloc(rt->sw_if_target, 16);//vec:对数组的操作
return 0;
}
//命令初始化
VLIB_INIT_FUNCTION(loadbalancer_init);
VPP多网口数据接收与转发
bridgeloadbalancer完整代码实现
vppplugins_LTLIBRARIES += bridgeloadbalancer_plugin.la
bridgeloadbalancer_plugin_la_SOURCES = \
bridgeloadbalancer/bridge.c \
bridgeloadbalancer/bridge_node.c \
bridgeloadbalancer/loadbalancer.c \
bridgeloadbalancer/loadbalancer_node.c
nobase_apiinclude_HEADERS += \
bridgeloadbalancer/bridge.h \
bridgeloadbalancer/loadbalancer.h
#include "loadbalancer.h"
#include <vnet/plugin/plugin.h>
int loadbalancer_set_targets(loadbalancer_main_t *lb, u32 sw_if_index_source,
u32 *sw_if_index_targets) {
u32 *sw_if_index;
debug("debug:[%s:%s:%d] loadbalancer_node.index:%d\n", __FILE__, __func__, __LINE__, loadbalancer_node.index);
loadbalancer_runtime_t *rt = vlib_node_get_runtime_data(lb->vlib_main, loadbalancer_node.index);
debug("debug:[%s:%s:%d] sw_if_index_source:%d\n", __FILE__, __func__, __LINE__, sw_if_index_source);
rt->sw_if_index_source = sw_if_index_source;
vec_foreach(sw_if_index, sw_if_index_targets) {
vec_add1(rt->sw_if_target, *sw_if_index);
}
return 0;
}
static clib_error_t *set_lb_target_command_fn (vlib_main_t * vm,
unformat_input_t * input,
vlib_cli_command_t * cmd) {
//flowtable_main_t *fm = &flowtable_main;
loadbalancer_main_t *lb = &loadbalancer_main;
loadbalancer_runtime_t *rt = vlib_node_get_runtime_data(vm, loadbalancer_node.index);
//loadbalancer_main_t *lb = rt->lbm;
u32 sw_if_index_source = ~0;
u32 sw_if_index = ~0;
u32 *sw_if_index_targets = rt->sw_if_target;
int source = 1;
debug("debug:[%s:%s:%d] input: %s, vnet_main: %p\n", __FILE__, __func__, __LINE__,
input->buffer, lb->vnet_main);
while (unformat_check_input(input) != UNFORMAT_END_OF_INPUT) {
if (unformat(input, "to")) {
source = 0;
} else if (unformat(input, "%U", unformat_vnet_sw_interface,
lb->vnet_main, &sw_if_index)) {
debug("debug:[%s:%s:%d] sw_if_index:%d\n", __FILE__, __func__, __LINE__, sw_if_index);
if (source) {
sw_if_index_source = sw_if_index;
} else {
vec_add1(sw_if_index_targets, sw_if_index);
}
} else break;
debug("[%s:%s:%d]: token: %s\n", __FILE__, __func__, __LINE__, input->buffer);
}
if (sw_if_index == ~0) {
return clib_error_return(0, "No Source Interface specified");
}
if (vec_len(sw_if_index_targets) <= 0) {
return clib_error_return(0, "No Target Interface specified");
}
int rv = loadbalancer_set_targets(lb, sw_if_index_source, sw_if_index_targets);
if (rv) {
return clib_error_return(0, "set interface loadbalancer return %d", rv);
}
return 0;
}
VLIB_CLI_COMMAND(set_interface_loadbalanced_command) = {
.path = "set interface bridge loadbalanced",
.short_help = "set interface bridge loadbalanced <interface> to <interfaces-list>",
.function = set_lb_target_command_fn,
};
#include <vppinfra/error.h>
#include <vnet/vnet.h>
#include <vnet/ip/ip.h>
#include <vppinfra/pool.h>
#include <vppinfra/bihash_8_8.h>
#define debug printf
#define foreach_loadbalancer_error _(PROCESSED, "Loadbalancer packets gone thru")
typedef enum {
#define _(sym,str) LOADBALANCER_ERROR_##sym,
foreach_loadbalancer_error
#undef _
LOADBALANCER_N_ERROR
} loadbalancer_error_t;
typedef struct {
vlib_main_t *vlib_main;
vnet_main_t *vnet_main;
} loadbalancer_main_t;
typedef struct {
loadbalancer_main_t *lbm;
u32 sw_if_index_source;
u32 *sw_if_target;
u32 last_target_index;
} loadbalancer_runtime_t;
loadbalancer_main_t loadbalancer_main;
vlib_node_registration_t loadbalancer_node; //
#include "loadbalancer.h"
#include <vppinfra/types.h>
#include <flowtable/flowtable.h>
//vlib_node_registration_t loadbalancer_node;
typedef enum {
LB_NEXT_DROP,
LB_NEXT_INTERFACE_OUTPUT,
LB_NEXT_N_NEXT
} loadbalancer_next_t;
typedef struct {
u32 sw_if_in;
u32 sw_if_out;
u32 next_index;
} lb_trace_t;
static u8 * format_loadbalance(u8 *s, va_list *args) {
CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
lb_trace_t *t = va_arg(*args, lb_trace_t*);
s = format(s, "LoadBalance - sw_if_in %d, sw_if_out %d, next_index = %d",
t->sw_if_in, t->sw_if_out, t->next_index);
return s;
}
static uword load_balance (vlib_main_t *vm,
vlib_node_runtime_t *node, vlib_frame_t *frame) {
u32 *to_next, *from = vlib_frame_vector_args(frame);
u32 n_left_from = frame->n_vectors;
u32 next_index = node->cached_next_index;
u32 pkts_processed = 0;
loadbalancer_runtime_t *rt = vlib_node_get_runtime_data(vm, loadbalancer_node.index);
while (n_left_from > 0) {
u32 pi0;
u32 next0;
u32 n_left_to_next;
//debug("debug:[%s:%s:%d] next_index:%d, n_left_to_next: %d\n", __FILE__, __func__, __LINE__, next_index, n_left_to_next);
vlib_get_next_frame(vm, node, next_index, to_next, n_left_to_next);
//debug("debug:[%s:%s:%d] next_index:%d, n_left_to_next: %d\n", __FILE__, __func__, __LINE__, next_index, n_left_to_next);
while (n_left_from > 0 && n_left_to_next > 0) {
next0 = LB_NEXT_INTERFACE_OUTPUT;
pi0 = to_next[0] = from[0];
vlib_buffer_t *b0 = vlib_get_buffer(vm, pi0);
pkts_processed ++;
flow_info_t *fi = *(flow_info_t**)b0->opaque;
u32 sw_if_index0 = fi->lb.sw_if_index_current;
fi->offloaded = 1;
fi->cached_next_node = FT_NEXT_INTERFACE_OUTPUT;
fi->lb.sw_if_index_rev = rt->sw_if_index_source;
rt->last_target_index ++;
if (rt->last_target_index >= vec_len(rt->sw_if_target)) {
rt->last_target_index = 0;
}
fi->lb.sw_if_index_dst = rt->sw_if_target[rt->last_target_index];
vnet_buffer(b0)->sw_if_index[VLIB_RX] = sw_if_index0;
if (sw_if_index0 == rt->sw_if_index_source) {
vnet_buffer(b0)->sw_if_index[VLIB_TX] = fi->lb.sw_if_index_rev;
} else {
vnet_buffer(b0)->sw_if_index[VLIB_TX] = fi->lb.sw_if_index_dst;
}
from ++;
to_next ++;
n_left_from --;
n_left_to_next --;
if (b0->flags & VLIB_BUFFER_IS_TRACED) {
lb_trace_t *t = vlib_add_trace(vm, node, b0, sizeof(*t));
t->sw_if_in = sw_if_index0;
t->next_index = next0;
t->sw_if_out = vnet_buffer(b0)->sw_if_index[VLIB_TX];
}
vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next, n_left_to_next, pi0, next0);
}
vlib_put_next_frame(vm, node, next_index, n_left_to_next);
}
vlib_node_increment_counter(vm, loadbalancer_node.index, LOADBALANCER_ERROR_PROCESSED, pkts_processed);
return frame->n_vectors;
}
static char *loadbalancer_error_strings[] = {
#define _(sym,string) string,
foreach_loadbalancer_error
#undef _
};
VLIB_REGISTER_NODE(loadbalancer_node) = {
.function = load_balance,
.name = "load-balance",
.vector_size = sizeof(u32),
.format_trace = format_loadbalance,
.type = VLIB_NODE_TYPE_INTERNAL,
.runtime_data_bytes = sizeof(loadbalancer_runtime_t),
.n_errors = LOADBALANCER_N_ERROR,
.error_strings = loadbalancer_error_strings,
.n_next_nodes = LB_NEXT_N_NEXT,
.next_nodes = {
[LB_NEXT_DROP] = "error-drop",
[LB_NEXT_INTERFACE_OUTPUT] = "interface-output",
}
};
static clib_error_t *loadbalancer_init(vlib_main_t *vm) {
loadbalancer_main_t *lbm = &loadbalancer_main;
loadbalancer_runtime_t *rt = vlib_node_get_runtime_data(vm, loadbalancer_node.index);
printf("loadbalancer_init\n\n");
rt->lbm = lbm;
lbm->vlib_main = vm;
lbm->vnet_main = vnet_get_main();
vec_alloc(rt->sw_if_target, 16);
return 0;
}
VLIB_INIT_FUNCTION(loadbalancer_init);
#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>
#include <arpa/inet.h>
typedef enum
{
CK_SAMPLE_NEXT_IP4,
CK_SAMPLE_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;
}
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;
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;
b0 = vlib_get_buffer(vm, bi0);
ip4_header_t *ip0 = vlib_buffer_get_current(b0);
ip4_address_t ip_src = ip0->src_address;
ip4_address_t ip_dst = ip0->dst_address;
u32 sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];
struct in_addr addr;
addr.s_addr = ip_src.as_u32;
printf("sw_if_index0: %d, ip_src: %s ", sw_if_index0, inet_ntoa(addr));
addr.s_addr = ip_dst.as_u32;
printf(" ip_dst: %s \n", inet_ntoa(addr));
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 (bridge_node) = {
.name = "bridge-node",
.function = ck_sample_node_fn,
.vector_size = sizeof(u32),
.format_trace = format_ck_sample_trace,
.type = 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,
.next_nodes = {
[CK_SAMPLE_NEXT_IP4] = "ip4-lookup",
[CK_SAMPLE_DROP] = "error-drop",
},
};
#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 bridge_node;
#define CK_SAMPLE_PLUGIN_BUILD_VER "1.0"
#endif /* __included_ck_sample_h__ */
#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)
{
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("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) {
if (unformat(input, "disable"))
enable_disable = 0;
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;
}
VLIB_CLI_COMMAND (ck_sample_command, static) = {
.path = "bridge",
.short_help =
"bridge <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();
printf("ck_sample_init\n\n");
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"),
};
*/