• 六、vpp 流表+负载均衡


    草稿!!!

    vpp node其实就是三个部分
    1、plugin init
    2、set command
    3、function 实现功能,比如这里的流表

    今天我们再用VPP实现一个流表的功能

    一、流表

    1.1流表----plugin init

    VLIB_REGISTER_NODE 注册流表节点

    // 注册流表节点
    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",
            [FT_NEXT_INTERFACE_OUTPUT] = "interface-output",
        } // 下一个节点的映射
    };
    
    
    // 注册插件
    VLIB_PLUGIN_REGISTER() = {
        .version = "1.0", // 插件版本
        .description = "sample of flowtable", // 插件描述
    };
    
    • 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

    1.2 流表-----command 解析

    接收到命令后,调用注册好的回调函数flowtable_command_enable_fn,最后会通过VPP自带的vnet_hw_interface_rx_redirect_to_node函数将硬件接口的接收流量重定向到指定节点,这里就是我们的流表节点

    // 启用或禁用流表功能,将硬件接口的接收流量重定向到指定节点
    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);
    
    	// 调用 VPP 的函数,将硬件接口的接收流量重定向到指定节点
    	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){
    		// 如果输入中包含 "disable" 参数,则禁用流表功能
    		if(unformat(input,"disable")){
    			enable_disable = 0;
    		}
    		// 如果输入中包含接口索引,则解析并存储在 sw_if_index 中
    		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\n",rv);
    		}
    	}
    	return 0;
    }
    
    //命令行启动、关闭流表功能
    VLIB_CLI_COMMAND(flowtable_interface_enable_disable_command) = {
    
    	.path = "flowtable",
    	.short_help = "flowtable  [disable]",
    	.function = flowtable_command_enable_fn, //对应的flowtable命令行回调函数
    };
    
    • 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
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    1.3 流表------function

    经过上面,这是当网卡接收到数据,就会传到我们指定的流表功能函数进行处理

    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;
    	
    	printf("flowtable_getinfo cached_next_index: %u\n", 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_put_next_frame将当前节点处理的数据包添加到这个帧中,
    		以便后续的节点可以处理这些数据包,最开始这里是获取的第一帧,一帧里面有多个vlib_buffer
    		可以看做为二维数组
    		*/
    		vlib_get_next_frame(vm,node,next_index,to_next,n_left_to_next);
    
    		/*n_left_to_next 是当前帧中剩余的空闲槽位数量,当值为0后,需要vlib_put_next_frame获取一个
    		新的帧来继续传递数据包
    		*/
                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;
    
    			//它将 DPDK 的 rte_mbuf 转换为 VPP 的 vlib_buffer_t,vlib_buffer_t里面有多个rte_mbuf(数据包)
    			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;
    			//获取处理的数据包所到达的接口的软件索引,并将其存储在变量 sw_if_index0 中
    			u32 sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];
    
    			//对每个数据包打印其源ip及目的ip
    			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));
    
    			/*
    			将当前处理的缓冲区 bi0 传递到下一个节点,同时更新 to_next 数组、n_left_to_next 计数
    			和下一个节点的下一个节点索引 next0。这样,数据包将会在当前节点处理后传递到下一个节点进行进一步处理
    			*/
    			vlib_validate_buffer_enqueue_x1(vm,node,next_index,
    				to_next,n_left_to_next,bi0,next0);
    		}
    		/*
    		vlib_get_next_frame获取下一个节点的帧和向量,然后vlib_put_next_frame将当前节点处理的数据
    		包添加到这个帧中,		以便后续的节点可以处理这些数据包
    		*/
    		vlib_put_next_frame(vm,node,next_index,n_left_to_next);
    	}
    	return frame->n_vectors; //这里决定接下来继续走哪一个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
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    这里的function函数有个返回值,根据返回值决定下一个节点走哪里
    flowtable_getinfo这里处理函数返回2 ,则下一步走这个节点[FT_NEXT_ETHERNET_INPUT] = “ethernet-input”,
    在这里插入图片描述

    浅谈node

    在一些系统中,节点的执行顺序可能是在程序初始化的时候静态确定的,特别是在构建数据处理流水线时。这种情况下,节点的顺序是在配置或初始化阶段定义的,然后在整个程序运行期间保持不变。

    然而,在某些系统中,特别是对于一些灵活的数据流框架,节点的执行顺序可能是在程序运行时动态决定的。这可以通过某种策略或运行时的条件来调整节点的执行顺序,以适应实时需求或系统的动态变化。

    总的来说,节点的执行顺序是取决于应用程序设计和需求的。一些系统更倾向于静态的、在初始化时确定的执行顺序,而其他系统则更注重在运行时根据动态需求来调整节点的执行顺序

    在许多系统中,插件和节点的初始化顺序通常是通过配置文件、命令行参数或其他配置机制来确定的。这些配置通常在应用程序启动时被解析,并在初始化过程中用于指导插件和节点的加载和初始化。

    以下是一些可能的方式来决定插件和节点的顺序:

    配置文件: 应用程序可能会有一个配置文件,其中包含有关插件和节点的信息,包括它们的加载顺序。在应用程序启动时,解析配置文件并按照其中定义的顺序加载插件和节点。

    命令行参数: 应用程序可能允许通过命令行参数来指定插件和节点的加载顺序。例如,通过在启动命令中指定参数来控制加载的插件和它们的顺序。

    硬编码: 在某些情况下,加载顺序可能是硬编码在应用程序的源代码中的,即在代码中明确指定加载和初始化的顺序。

    依赖关系: 插件和节点之间可能存在依赖关系,系统可以根据这些依赖关系来确定加载和初始化的顺序。例如,某个插件可能依赖于另一个插件的某些功能,因此必须在其之前加载。

    具体的实现方式取决于系统的设计和开发者的选择。在某些情况下,可能会结合使用上述多种机制来达到更大的灵活性。

  • 相关阅读:
    opencv入门
    【数据结构】最小生成树(Kruskal算法)
    混沌学院-亚朵酒店案例-观后随心谈
    no main manifest attribute, in xxx.jar
    idea默认带的equals和hashcode引起的bug
    手撕前端面试题(Javascript~事件委托、数组去重、合法的URL、快速排序、js中哪些操作会造成内存泄漏......
    三百内蓝牙耳机哪款好?内行盘点四款三百内最好的蓝牙耳机
    【HTML】HTML基础6.1(表格以及常见属性)
    Sun Solaris 修改IP地址或者主机名
    微信小程序_22,全局数据共享
  • 原文地址:https://blog.csdn.net/Phoenix_zxk/article/details/133469725