VPP 负载均衡测试代码

1. 均衡的测试思想和流程说明。

先说一下理论, 然后后边才知道 代码逻辑。

调试了两天,这个代码终于通了。 由于时间关系, 画了一个粗略的图。另外这个代码只是流程通了,不过要帮助理解负载均衡我认为已经足够了。

下面是windows 电脑发UDP 包给VPP 进行均衡的流程图:

总的来说就VPP中有两个NODE, 一个是flowtable, 一个是load balancer.

这个插件跑起来后(后边说如何跑),在VPP 中输入以下命令:

set interface loadbalanced GigabitEthernet13/0/0  to  GigabitEthernet1b/0/0

后, 就会把GigabitEthernet13/0/0 的报文全部重定向到flowtable NODE, flowtable node处理后(选择一些报文, 我们例子中选择全部) 转发到load balancer node. 然后load balancer node把报文按均衡算法从 GigabitEthernet1b/0/0 口发出去, 其实目的port 可以多设置几个, 不过我的VPP中我只加了两个Port 就只设置了一个目的 port。

图中的示例是从windows 电脑 192.168.100.103 发一个udp包给 192.168.100.200。 windows 要先发一个ARP 包, 来获取目的IP的MAC 地址, 这个ARP 广播报文到达192.168.100.200 后, 直接重定向到了flow table NODE. 在flowtable node中我把ARP 报文改成了回复报文(RARP), 然后送到了load balancer node,  然后load balance node 把包从192.168.100.201 发出去, 到达了windows 电脑。 这样windows 电脑就知道192.168.100.200 在哪里了。

windows电脑第二步发了一个UDP 包给192.168.100.200, 这个UDP 包到达192.168.100.200 后, 直接重定向到了flow table NODE. 在flowtable node中我没有做任何处理, 然后送到了load balancer node,  然后load balance node 把这个包从192.168.100.201 发出去, 因为包的内容 还是发给192.168.100.200的, 所以又送给了192.168.100.200 , 这样就死循环了。

不过没有关系 , 我们的转发, 和均衡功能 其实已经达到测试的目的 了。

2. 如何实现这个功能, 我们直接上源码:注释写得很详细了, 加上上边的说明应该没有问题。

在VPP中创建文件夹 vpp/src/plugins/flowtable , 放入以下这些文件 :

loadbalancer.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;


extern vlib_node_registration_t loadbalancer_node;



loadbalancer.c



#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);
	loadbalancer_runtime_t *rt = vlib_node_get_runtime_data(lb->vlib_main, loadbalancer_node.index); // 初始化时定义了runtime_t 内部的大小, 这里存出来, 存自己想要存的东西。

	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; // 存源Port
	vec_foreach(sw_if_index, sw_if_index_targets) { // 存目的port list
		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;
	
	u32 sw_if_index_source = ~0;
	u32 sw_if_index = ~0;
	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 {
				vec_add1(sw_if_index_targets, sw_if_index); //解析PACKT 被哪些口进行均衡。
			} 
		} 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); // 把源接口重定向到flowtable NODE
//	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); // 把源port , 目的port 存到runtime_t中。
	if (rv) {
		return clib_error_return(0, "set interface loadbalancer return %d", rv);
	}
	return 0;
}


VLIB_CLI_COMMAND(set_interface_loadbalanced_command) = {  // 设置loadbalancer NODE的命令行和解析 函数
	.path = "set interface loadbalanced",
	.short_help = "set interface loadbalanced <interface> to <interfaces-list>",
	.function = set_lb_target_command_fn,
};


loadbalancer_node.c



#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;

}


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); // 取得runtime buffer, 主要提取源port, 目的port list信息。

	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 lb node:[%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; // 和vnet_buffer(b0)->sw_if_index[VLIB_TX] =xx 一块使用, 达到从指定port发出去的目的
			pi0 = to_next[0] = from[0];
			debug("== 1\n");

			vlib_buffer_t *b0 = vlib_get_buffer(vm, pi0);
			
			debug("== 2\n");

			pkts_processed ++;
			debug("== 3\n");

			//flow_info_t *fi = *(flow_info_t**)b0->opaque; // 这个什么意思 ?这里换成下边的试下:
			flow_info_t *fi = (flow_info_t*)b0->opaque; //其实就是把这个buffer 转成自己想要存的数据, 这样这个buffer到其他node后不用重新解析。
			
			debug("== 4\n");

			{
				//b0 = vlib_get_buffer(vm, bi0);
                    
				u8 *ip0 = vlib_buffer_get_current(b0);
				{   int i;
					printf("lb node pkt:");
					for(i = 0; i <100; i++)
					{
						printf("%02x ", *((u8*)ip0 + i));
					}
					printf("\n");	
				}
			}
			

			//u32 sw_if_index0 = fi->lb.sw_if_index_current; // 应该是挂在这里了。 这行关了换成下边的试下:这种用法不对, 会挂,
			u32 sw_if_index0;
			{
				debug("vnet_buffer(b0)->sw_if_index[VLIB_RX]: %d \n ", vnet_buffer(b0)->sw_if_index[VLIB_RX]); // 取出包来自哪个port
				sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];
			}

			
			debug("== 5\n");
			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 ++;
			debug("== 6\n");

			if (rt->last_target_index >= vec_len(rt->sw_if_target)) {
				rt->last_target_index = 0;
			}

			debug("== 7\n");

			fi->lb.sw_if_index_dst = rt->sw_if_target[rt->last_target_index];

			debug("== 8\n");

			vnet_buffer(b0)->sw_if_index[VLIB_RX] = sw_if_index0; // 这里buffer里本来不是原来的源port, 不知道重赋值的意义?
			debug("== 9\n");

//			if (sw_if_index0 == rt->sw_if_index_source) {
//				debug("== 9 -1 send back to int %d\n",  fi->lb.sw_if_index_rev);
//				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; // flowtable 转发port1(192.168.100.200)过来的ARP 包, 因为我们已经修改成RARP 包。
				                                                                //从这里发出去后, 我们windows电脑上就知道port 1(192.168.100.200)的位置了。 
				                                                                // 但是有一个问题, 其他包, 比如windows电脑发给port1的UDP 包, 我们没有做任何处理就发出去,
				                                                                // 系统会把这个包又发给port1, 然后这个包会一直在这里转圈, 导致虚拟网络不通。 现实中应该不会存在这种情况, 会做其他处理。
				debug("== 9 -2 send to int %d\n", fi->lb.sw_if_index_dst);
//			}

			debug("== 10\n");

			from ++;
			to_next ++;
			n_left_from --;
			n_left_to_next --;

			debug("== 11\n");

			if (b0->flags & VLIB_BUFFER_IS_TRACED) {
				lb_trace_t *t = vlib_add_trace(vm, node, b0, sizeof(*t)); // 增加trace信息, 后边再说怎么用
				t->sw_if_in = sw_if_index0;
				t->next_index = next0;
				t->sw_if_out = vnet_buffer(b0)->sw_if_index[VLIB_TX];
			}

			debug("== 12\n");

			vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next, n_left_to_next, pi0, next0); // 校验下一NODE index
		}
		
		debug("== 13\n");

		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,   // 注册node处理函数
	.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", // 我们把buffer_t->sw_if_index_[VLIB-TX ] 设置好, 然后push到这个Node, 就会从对应 port 发出去了。
	}
};

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); // 初始化runtime_t buffer

	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);



flowtable.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 struct {
  u16 ar_hrd;			/* format of hardware address	*/
  u16 ar_pro;			/* format of protocol address	*/
  u8  ar_hln;			/* length of hardware address	*/
  u8  ar_pln;			/* length of protocol address	*/
  u16 ar_op;			/* ARP opcode (command)		*/
  u8  ar_sha[6];		/* sender hardware address	*/
  u8  ar_spa[4];		/* sender IP address		*/
  u8  ar_tha[6];		/* target hardware address	*/
  u8  ar_tpa[4];		/* target IP address		*/
} arp_ether_ip4_t; // 这个是解析ARP包时要用的。


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; // 这里是定义下一跳NODE 集合下标


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; // 这个是用于将vlib_buffer_t中opaque[] 转换成我们自己要存的数据格式用的。 


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);



flowtable.c




#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); // 这里就是这个例子中, 最开始要设置的地方, 能过命令, 把接口报文 转发到flowtable NODE。
}


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;
//	int featureFlag = 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");
	}

	// 调用库函数操作这个node
	//vnet_feature_enable_disable("ip4-unicast", "flow_table", sw_if_index, enable_disable, 0, 0);

	int rv = flowtable_enable(fm, sw_if_index, enable_disable); // 根据参数设置转发的源。 实际上这个FLOW table的从行这个例子中都 用不上。 是直接 用loadbalancer node的命令行。
	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,

};






flowtable_node.c



#include "flowtable.h"
#include <vnet/plugin/plugin.h>

#include <arpa/inet.h>
#include <vnet/ethernet/packet.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) { // 这个例子中用于trace 用途。

	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;

}

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);
    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);  // 这里收到的包, 可能是以太网包, 可能是IP 包, 不应该是直接转换面IP 包。
//					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;
					
//					{   int i;
//						for(i = 0; i <100; i++)
//						{
//							printf("%02x ", *((u8*)ip0 + i));
//						}
//						printf("\n");
//
//						
//					}
					{
						ethernet_header_t* ethP = vlib_buffer_get_current (b0);
						arp_ether_ip4_t *arpP =(arp_ether_ip4_t *) (ethP + 1);
                        printf("eth type: 0x%04x op 0x%02x\n", ethP->type, arpP->ar_op); 
						if(0x0608 ==  ethP->type  && 0x0100 == arpP->ar_op) // 这里大小端转换偷懒了下, 增手动调换了下。
						{
							u8 tmpMac[6];
							u8 myMac[6];

							myMac[0] = 0x00 ;//Ethernet address 00:0c:29:b6:43:6c。 这里是本地自己查出来的本地interface 的MAC 地址。 hardcode 在这里, 发回给ARP 源头。后边interface才能接收到来自UDP 发包源包
							myMac[1] = 0x0c ;
							myMac[2] = 0x29 ;
							myMac[3] = 0xb6 ;
							myMac[4] = 0x43 ;
							myMac[5] = 0x6c ;
							
							int i;
							// opcode change to 2
							arpP->ar_op = 0x0200;
							
							// ehternet pkt, DMAC, SRC exchange.
							for(i = 0; i < 6; i++)
							{
								tmpMac[i] = ethP->dst_address[i];
								ethP->dst_address[i] = ethP->src_address[i];
								ethP->src_address[i] = myMac[i]; 

								tmpMac[i] = arpP->ar_sha[i];
								arpP->ar_tha[i] = tmpMac[i];
								arpP->ar_sha[i] = myMac[i];
							}
							
							// arp content , exchange sip, smac, dip, dmac.
							for(i = 0; i < 4; i++)
							{
								tmpMac[i] = arpP->ar_spa[i];
								arpP->ar_spa[i] = arpP->ar_tpa[i];
								arpP->ar_tpa[i] = tmpMac[i];
							}

							printf("flowtable node ARP BACK: ");
							{   int i;
								for(i = 0; i <100; i++)
								{
									printf("%02x ", *((u8*)ip0 + i));
								}
								printf("\n");	
							}
							
						}
					}

					
					//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;
//					}
                    next0 = FT_NEXT_LOAD_BALANCER; // send to load balanceer node, 这里是直接把包转给load balancer node 进行处理。 按理说这里是直接转的, 那其实我觉得flow table这个node 都没有必要了。
                                                   // 那有一种可能, 就是这里可以选择一些我们要进行负载均衡的包, 然后不要的就drop掉。
                                                   // 例如: 我们要让其他PC 能ping 通这个interface, 那这里的ARP 包和ICMP包就要进行特殊处理。 
					printf("============= next0: %d \n", next0);
					
                    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;

}

static char *flowtable_error_strings[] = {
#define _(sym,string) string,
  foreach_flowtable_error
#undef _
};


VLIB_REGISTER_NODE(flowtable_node) = {

	.function = flowtable_getinfo,
	.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 = {
		[FT_NEXT_IP4]    = "ip4-lookup",
		[FT_NEXT_DROP] = "error-drop",
		[FT_NEXT_ETHERNET_INPUT] = "ethernet-input",
		[FT_NEXT_LOAD_BALANCER] = "load-balance",   // 这个例子中, 我们只用了这个NODE, 实际中, 可以加一些判断, 把其他 几个NODE也用起来。
		[FT_NEXT_INTERFACE_OUTPUT] = "interface-output",
	}
};




VLIB_PLUGIN_REGISTER() = {

	.version = "1.0",
	.description = "sample of flowtable",

};


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;
}

VLIB_INIT_FUNCTION(flowtable_init);

//VNET_FEATURE_INIT(flow_table, static) =   
//{
//	.arc_name = "ip4-unicast",
//	.node_name = "flow_table",
//	.runs_before = VNET_FEATURES("ip4-lookup"),
//};



3. 配置编译文件 :

cd vpp/src

 vi configure.ac

增加以下这行

vpp/src/plugins 目录下增加 flowtable.am

# Copyright (c) 2015 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.

libflowtable_plugin_la_SOURCES =                        \
        flowtable/flowtable.c                           \
        flowtable/flowtable_node.c                      \
        flowtable/loadbalancer.c                        \
        flowtable/loadbalancer_node.c

noinst_HEADERS +=                               \
        flowtable/flowtable.h                           \
        flowtable/loadbalancer.h

vppplugins_LTLIBRARIES += libflowtable_plugin.la

# vi:syntax=automake

vpp/src/plugins 目录下在Makefile.am中增加以下行

4. 编译和运行。

cd vpp

make wipe

make build

make run

这样就可以运行VPP了。运行起来后先敲 quit 退出 。

5. 跑测试

1). dpdk 安装igb_uio, 并绑定相关虚拟网卡(参考我另一封pkt dumpme 的测试demo).

2). cd vpp

make run 这样去后设置以下命令:

 这个时候就可以在windows 电脑发送UDP 包了。

6. windows上用串口工具发送UDP 测试包

发送的112233445566778899

我们先看下windows 上是不是收到VPP的ARP 回复包:

确实收到了, 那VPP 应该能收到UDP 包了, 查看VPP上的debug 打印:

网上有很多的示例, 但都没有调试过程。 楼主调试了几天, 分享的代码可以直接使用,希望能帮助到志同道合的朋友。 

如果对你有帮助, 请留下宝贵的赞, 哈哈。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/531997.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

什么是企业邮箱?如何选择合适的企业邮箱?

企业邮箱和个人邮箱不同&#xff0c;它的邮箱后缀是企业自己的域名。企业邮箱供应商一般都提供手机app、桌面端、web浏览器访问等邮箱使用途径。那么什么是企业邮箱&#xff1f;如何选择合适的企业邮箱&#xff1f;好用的企业邮箱应具备无缝迁移、协作、多邮箱管理等功能。 企…

Docker篇(二)— Docker架构介绍

目录 一、Docker和虚拟机的区别二、Docker架构镜像和容器DockerHubDocker架构 小结 一、Docker和虚拟机的区别 Docker可以让一个应用在任何操作系统中非常方便的运行。而以前我们接触的虚拟机&#xff0c;也能在一个操作系统中&#xff0c;运行另外一个操作系统&#xff0c;保…

智能面试——录音及播放下载js-audio-recorder — post请求,formdata传参

录音插件 js-audio-recorder bug&#xff1a;本地调试调取不起来麦克风 浏览器配置安全域名 chrome://flags/Insecure origins treated as secure输入域名即可电脑需要连接上耳机 <template><div class"BaseRecorder"><div class"BaseRecorder-r…

产品开发流程

产品开发流程 时间&#xff1a;2024年04月10日 作者&#xff1a;小蒋聊技术 邮箱&#xff1a;wei_wei10163.com 微信&#xff1a;wei_wei10 产品开发流程_小蒋聊技术_免费在线阅读收听下载 - 喜马拉雅欢迎收听小蒋聊技术的类最新章节声音“产品开发流程”。时间&#xff1a;…

单链表专题

文章目录 目录1. 链表的概念及结构2. 实现单链表2.1 链表的打印2.2 链表的尾插2.3 链表的头插2.4 链表的尾删2.5 链表的头删2.6 查找2.7 在指定位置之前插入数据2.8 在指定位置之后插入数据2.9 删除pos节点2.10 删除pos之后的节点2.11 销毁链表 3. 链表的分类 目录 链表的概念…

设计模式学习笔记 - 设计模式与范式 -行为型:10.迭代器模式(中):遍历集合时,为什么不能增删集合?

概述 上篇文章&#xff0c;我们通过给 ArrayList 和 LinkedList 容器实现迭代器&#xff0c;学习了迭代器模式的原理、实现和设计意图。迭代器模式主要主要是解耦容器代码和遍历代码。 本章&#xff0c;我们来深挖一下&#xff0c;如果在使用迭代器遍历集合的同时增加、删除集…

无尘净化棉签:清洁革新的里程碑

随着科技的不断进步&#xff0c;日常生活中的许多小物件也在不断地得到创新和改良。其中&#xff0c;棉签作为一种常见的清洁工具&#xff0c;经历了从传统到现代的革新&#xff0c;引入了无尘棉签的概念&#xff0c;为清洁领域带来了一场革命性的变革。本文优斯特将探讨无尘棉…

运维工具-Backup集合

RepositoryLicenseStarCreatedAtUpdatedAtDescriptionjeessy2/backup-xMIT2842021-11-132023-12-15带Web界面的数据库/文件备份增强工具noovertime7/gin-mysqlbakMIT382022-06-212023-02-06一款分布式高性能的备份系统&#xff0c;支持 MySQL、ElasticSearch 备份&#xff0c;多…

《高通量测序技术》分享,生物信息学生信流程的性能验证,以肿瘤NGS基因检测为例。

这是这本书&#xff0c;第四章第五节的内容&#xff0c;这一部分是以NGS检测肿瘤基因突变为例&#xff0c;描述了其原理和大概流程&#xff0c;这和以前我分享的病原宏基因组高通量测序性能确认方案可以互相补充&#xff0c;大家可以都看一下&#xff0c;但是想要真正的弄懂&am…

【Leetcode】1702. 修改后的最大二进制字符串

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 给你一个二进制字符串 b i n a r y binary binary &#xff0c;它仅有 0 0 0 或者 1 1 1 组成。你可以使用下面的操作任意次对它进行修改&#xff1a; 操作 1 &#xff1a;如果…

背 单 词

单词&#xff1a; 买考研词汇闪过 研究艾宾浩斯遗忘曲线 https://www.bilibili.com/video/BV18Y4y1h7YR/?spm_id_from333.337.search-card.all.click&vd_source5cbefe6dd70d6d84830a5891ceab2bf9 单词方法 闪记背两排&#xff08;5min&#xff09;重复一遍&#xff08;2mi…

解决Can‘t connect to HTTPS URL because the SSL module is not available

把C:\develop\An3\Library\bin的这些文件&#xff0c;复制到C:\develop\An3\DLLs中

2006年重邮801信号与系统考研真题与详解

本系列文章为重庆邮电大学801信号与系统考研真题与详解&#xff0c;前面文章链接如下&#xff1a; 2003年重邮801信号与系统考研真题与详解 2004年重邮801信号与系统考研真题与详解 2005年重邮801信号与系统考研真题与详解 文章目录 前言一对一极速提分辅导2006年重邮801信号与…

基于GAN的图像补全实战

数据与代码地址见文末 论文地址:http://iizuka.cs.tsukuba.ac.jp/projects/completion/data/completion_sig2017.pdf 1.概述 图像补全,即补全图像中的覆盖和缺失部分, 网络整体结构如下图所示,整体网络结构还是采取GAN,对于生成器,网络结构采取Unet的形式,首先使用卷积…

字节发布AnimateDiff-Lightning文生视频模型——可在线免费试玩

Sora文生视频大模型 随着Sora文生视频大模型的爆火&#xff0c;文生视频大模型必定是各大人工智能公司竞争的主要领域。虽然 Sora模型的视频效果绝对是领先地位&#xff0c;但是Sora模型目前还没有开放使用&#xff0c;我们并无法直接使用。前期我们也介绍过字节发布的MagicVi…

【优选算法专栏】专题十三:队列+宽搜(一)

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

C++ 线程库(thread)与锁(mutex)

一.线程库(thread) 1.1 线程类的简单介绍 thread类文档介绍 在C11之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;比如windows和linux下各有自己的接口&#xff0c;这使得代码的可移植性比较差。C11中最重要的特性就是对线程进行支持了&#xff…

群联AI云防护中的防盗链技术原理及其作用探析---

一、引言 随着云计算和AI技术的快速发展&#xff0c;云防护方案已经成为现代企业防范网络攻击和保护数字资产的重要手段之一。群联科技作为存储解决方案和技术服务的领导者&#xff0c;已将其AI技术应用于云端防护系统中&#xff0c;并特别强化了防盗链功能&#xff0c;以帮助…

element-plus blur和select冲突失效导致移除焦点清空文本框内容失效解决方案

问题 需求是做一个查询企业的功能&#xff0c;期望必须进行选择后进行查询&#xff0c;如果用户自主输入并没有进行选择&#xff0c;失去焦点的时候清空文本框里面的内容&#xff0c;给el-autocomplete添加blur事件后发现&#xff0c;在下拉没有出现之前快速失去焦点才能触发b…