openVswitch(OVS)源代码分析之工作流程(数据包处理),openvswitchovs


        上篇分析到数据包的收发,这篇开始着手分析数据包的处理问题。在openVswitch中数据包的处理是其核心技术,该技术分为三部分来实现:第一、根据skb数据包提取相关信息封装成key值;第二、根据提取到key值和skb数据包进行流表的匹配;第三、根据匹配到的流表做相应的action操作(若没匹配到则调用函数往用户空间传递数据包);其具体的代码实现在 datapath/datapath.c 中的,函数为: void ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb);当接受到一个数据包后,自然而然的就应该是开始对其进行处理了。所以其实在上篇的openVswitch(OVS)源代码分析之工作流程(收发数据包)中的接受数据包函数:void ovs_vport_receive(struct vport *vport, struct sk_buff *skb)中已有体现,该函数在最后调用了ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb);来把数据包传递到该函数中去进行处理。也由此可见所有进入到openVswitch的数据包都必须经过ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb);函数的处理。所以说ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb);是整个openVswitch的中间枢纽,是openVswitch的核心部分。

        对于ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb);的重要性已经解释的非常清楚,紧接着就应该分析该函数源代码了,在分析源代码之前还是得提醒下,其中涉及到很多数据结构,如果有些陌生可以到openVswitch(OVS)源代码分析之数据结构中进行查阅,最好能先大概的看下那文章,了解下其中的数据结构,对以后分析源代码有很大的帮助。

        下面来分析几个ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb);函数中涉及到但在openVswitch(OVS)源代码分析之数据结构又没有分析到的数据结构:

        第一个、是数据包的统计结构体,是CPU用来对所有数据的一个统计作用:

// CPU对给定的数据包处理统计
struct dp_stats_percpu {
	u64 n_hit; // 匹配成功的数据包个数
	u64 n_missed; // 匹配失败的数据包个数,n_hit + n_missed就是接受到的数据包总和
	u64 n_lost; // 丢失的数据包个数(可能是datapath队列溢出导致)
	struct u64_stats_sync sync;
};

        第二、是数据包发送到用户空间的参数结构体,在匹配流表没有成功时,数据将发送到用户空间。而内核空间和用户空间进行数据交互是通过netLinks来实现的,所以这个函数就是为了实现netLink通信而设置的一些参数:

// 把数据包传送给用户空间所需的参数结构体
struct dp_upcall_info {
	u8 cmd; // 命令,OVS_PACKET_CMD_ *之一
	const struct sw_flow_key *key; // key值,不能为空
	const struct nlattr *userdata; // 数据的大小,若为空,OVS_PACKET_ATTR_USERDATA传送到用户空间
	u32 portid; // 发送数据包的Netlink的PID,其实就是netLink通信的id号
};

        下面是来分析ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb);函数的实现源代码:

// 数据包的处理函数,openVswitch的核心部分
void ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb)
{
	struct datapath *dp = p->dp; // 定义网桥变量,得到端口所在的网桥指针
	struct sw_flow *flow; // 流表
	struct dp_stats_percpu *stats; // cpu中对数据包的统计
	struct sw_flow_key key; // skb中提取到的key值
	
	   	
	u64 *stats_counter;
	int error;
     // 这应该是linux内核中的,openVswitch中很多是根据linux内核设计而来的
     // 这里应该是对网桥中的数据包统计属性进行初始化
	stats = this_cpu_ptr(dp->stats_percpu);

     // 根据端口和skb数据包的相关值进行提取,然后封装成key值
	error = ovs_flow_extract(skb, p->port_no, &key);
	if (unlikely(error)) { // 定义宏来判断key值得提取封装是否成功
		kfree_skb(skb);// 如果没有成功,则销毁掉skb数据包,然后直接退出
		return;
	}

     // 调用函数根据key值对流表中所有流表项进行匹配,把结果返回到flow中
	flow = ovs_flow_lookup(rcu_dereference(dp->table), &key);
	if (unlikely(!flow)) { // 定义宏判断是否匹配到相应的流表项,如没有,执行下面代码
		struct dp_upcall_info upcall; // 定义一个结构体,设置相应的值,然后把数据包发送到用户空间
		// 下面是根据dp_upcall_info数据结构,对其成员进行填充
		upcall.cmd = OVS_PACKET_CMD_MISS;// 命令
		upcall.key = &key;// key值
		upcall.userdata = NULL;// 数据长度
		upcall.portid = p->upcall_portid;// netLink通信时的id号
		ovs_dp_upcall(dp, skb, &upcall);// 把数据包发送到用户空间
		consume_skb(skb);// 销毁skb
		stats_counter = &stats->n_missed;// 用未匹配到流表项的包数,给计数器赋值
		goto out;// goto语句,内部跳转,跳转到out处
	}
       // 在分析下面的代码时,先看下OVS_CB()这个宏:#define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb)
       // 这个宏如果知道skb数据结构的话,就好理解。大概的意思是把skb中保存的当前层协议信息的数据强转为ovs_skb_cb*数据指针
	OVS_CB(skb)->flow = flow;// 能够执行到这里,说明匹配到了流表。把匹配到的流表想flow赋值给结构体中成员
	OVS_CB(skb)->pkt_key = &key;// 同上,把相应的key值赋值到结构体变量中
      //  这是匹配成功的,用匹配成功的数据包数赋值于计数器变量
	stats_counter = &stats->n_hit;
	ovs_flow_used(OVS_CB(skb)->flow, skb);// 调用函数调整流表项成员变量(也许是用来流表项的更新)
	ovs_execute_actions(dp, skb); // 根据匹配到的流表项(已经在skb中的cb)执行相应的action操作

out: 
     // 这是流表匹配失败,数据包发到用户空间后,跳转到该处,
     // 对处理过的数据包数进行调整(虽然没匹配到流表,但也算是处理掉了一个数据包,所以计数器变量应该增加1)
	u64_stats_update_begin(&stats->sync);
	(*stats_counter)++;
	u64_stats_update_end(&stats->sync);
}
        上面就是openVswitch的核心部分,所有的数据包都要经过此函数进行逻辑处理。这只是一个逻辑处理的大体框架,还有一些细节(key值得提取,流表的匹配查询,数据传输到用户空间,根据流表执行相应action)将在后面分析。当把整个openVswitch的工作流程梳理清晰,会发现这其实就是openVswitch的头脑部分,所有的逻辑处理都在里实现,所以我们自己添加代码时,这里往往也是个不错的选择。

        如果看了前面那篇openVswitch(OVS)源代码分析之工作流程(收发数据包),那么应该记得其中也说到了可以在收发函数中添加自己代码,因为一般来说收发函数也是数据包的必经之地(发送函数可能不是)。那么怎么区分在哪里添加自己代码合适呢?

        其实在接受数据包函数中添加自己代码和在这里的逻辑处理函数中添加自己代码,没有多大区别,因为接受函数中没有做什么处理就把数据包直接发送打逻辑处理函数中去了,所以这两个地方添加自己代码其实是没什么区别的。但是从习惯和规范来说,数据包接受函数只是根据条件控制数据包的接受,并不对数据包进行逻辑上的处理,也不会对数据包进行修改等操作。而逻辑处理函数是会对数据包进行某些逻辑上的处理。(最明显的是修改数据包内的数据,一般来说接受数据包函数中是不会对数据包内容修改的,但逻辑处理函数则有可能会去修改的)。

        而在数据包发送函数中添加自己代码和逻辑函数中添加自己代码也有些区别,数据包发送函数其性质和接受函数一样,一般不会去修改数据包,而仅仅是根据条件判断该数据包是否发送而已。

        那下面就逻辑处理函数中添加代码来举例:

        假若要把某个指定的IP主机上发来的ARP数据包进行处理,把所有的请求数据包变成应答数据包,原路返回。这里最好就是把自己的代码添加到逻辑处理函数中去(如果你要强制的添加到数据包接受函数中去也可以),因为这里要修改数据包的内容,是一个逻辑处理。具体实现:可以在key值提取前对数据包进行判断,看是否是ARP数据包,并且是否是指定IP主机发来的。若不是,交给系统去处理;若是,则对Mac地址和IP地址进行交换,并且把请求标识变成应答标识;最后调用发送函数从原来的端口直接发送出去。这只是一个简单的应用,旨在说明逻辑处理代码最好添加到逻辑处理函数中去。如果要处理复杂的操作也是可以的,比如定义自己的流表,然后然后屏蔽掉系统的流表查询,按自己的流表来操作。这就是一个对openVswitch比较大的改造了,流表、action、流表匹配等这些openVswitch主要功能和结构都要自己去定义实现完成。

        以上分析得就是openVswitch的核心部分,当然了只是一个大体框架而已,后续将会逐步完善。

        转载请注明原文出处,原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/39378195

                                                                                                                 如有不正确之处,望大家指正,谢谢!!!

        




相关内容