多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
说点题外话(我不仅把这些当作技术文章,还当作工作笔记,甚至当作生活日记),最近工作在制作各种docker镜像,有点小忙。而工作剩余时间又在看汇编,和操作系统知识,所以对ovs就没什么时间去了解了。不过还好,这周空闲下来了,就看了下ovs中的upcall()函数调用。 话说现在ovs已经出了2.xxx版本了,我稍微浏览了下,发现有些函数名改变了,但其主要功能还是保留的。为了衔接前几篇blog,所以我还是选择下载1.xx版本的源代码来分析。我以前那套ovs源代码做了很多笔记,不过可惜搬公司的时候服务器坏掉了,所有数据都找不到了(因为分析这个源代码是个人行为、私事,所以也就没有去恢复硬盘了)。 还有个事要麻烦下,我分析这些源代码是以个人的观点和判断,我没有什么资料,就是一步一步的去分析,然后组成整个框架,其中当然免不了有些错误(人家是世界级团队完成的,你一个小程序员花这么点时间就想弄明白,那估计是不太可能的),所以我非常鼓励支持查资料的朋友仅仅是把我的分析当作一种参考,然后如果发现和我猜想的框架有问题时能及时告知我,谢谢!! 好了,下面正式谈谈和源代码有关的事了。我看了下upcall()函数的大体实现,其中主线是用Linux内核中的NetLink通信机制。而其中涉及到一些其他知识点,大部分在前面已经分析过了;但vlan知识点,在前面好像基本上没有提到,个人觉得这是个非常有价值的知识点,后续我会好好了解下。而有关ovs的前一篇[openVswitch(OVS)源代码分析 upcall调用(之linux中的NetLink通信机制)](http://blog.csdn.net/yuzhihui_no1/article/details/40790131)我现在到回去看了看,感觉没有写好,有点懊恼。有些东西写的不够仔细,太注重代码的实现了,而没写好一些理论的东西。如果要了解upcall()函数,那些基础的结构体还是要重点了解下,所以我会修改前面的NetLink分析或者到后面再分析下理论知识。 现在来想下为什么有upcall()函数?因为比如当第一个数据包过来时(前期没有和这个数据包的ip主机通信过),ovs中没有任何有关于该主机的信息,更没有设置一些规则来处理接受到该主机的数据包。所以当第一次接受到这个数据包时,就要提取出数据包中一些信息,下发到用户空间去,让用户空间做些规则用来处理下次接收到的该类数据包。 下面开始看代码,还是从void ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb)函数开始切入吧。 ~~~ if (unlikely(!flow)) {//查不到流表的情况 struct dp_upcall_info upcall; upcall.cmd = OVS_PACKET_CMD_MISS; //包miss,表示这个包是没匹配到的 upcall.key = &key; //key值,对一个sk_buff网络包的特征数据进行提取组成的结构 upcall.userdata = NULL; // 传送给用户空间的数据 upcall.portid = p->upcall_portid; //传送给用户空间时使用的id号,netlink中已经说明 ovs_dp_upcall(dp, skb, &upcall); // 调用函数处理,本blog的主角 consume_skb(skb); // 释放掉包结构==》kfree_skb(skb) stats_counter = &stats->n_missed; //对包的计算 goto out; } ~~~ 下面就是轮到今天的主角出场了: ~~~ int ovs_dp_upcall(struct datapath *dp, struct sk_buff *skb, const struct dp_upcall_info *upcall_info) { struct dp_stats_percpu *stats; int dp_ifindex; int err; // 判断下pid是否为0,这个是用来NetLink通讯使用的,为0表示传给内核空间的 if (upcall_info->portid == 0) { err = -ENOTCONN; goto err; } // 这个字段呢,是个设备结构索引号,等下详细分析,也是这篇blog的重点 dp_ifindex = get_dpifindex(dp); if (!dp_ifindex) { err = -ENODEV; goto err; } /* *     forward_ip_summed - map internal checksum state back onto native * kernel fields. * @skb: Packet to manipulate. * @xmit: Whether we are about send on the transmit path the network stack. * This follows the same logic as the @xmit field in compute_ip_summed(). * Generally, a given vport will have opposite values for @xmit passed to * these two functions. * When a packet is about to egress from OVS take our internal fields (including * any modifications we have made) and recreate the correct representation for * this kernel.  This may do things like change the transport header offset. */ forward_ip_summed(skb, true); //下面这两个函数就是要排队发送信息到用户空间的,不过这要到下一篇blog分析 if (!skb_is_gso(skb)) err = queue_userspace_packet(ovs_dp_get_net(dp), dp_ifindex, skb, upcall_info); else err = queue_gso_packets(ovs_dp_get_net(dp), dp_ifindex, skb, upcall_info); if (err) goto err; return 0; // 下面是出错时,跳转到这里做退出处理的,就是一些数据包的统计等操作 err: stats = this_cpu_ptr(dp->stats_percpu); u64_stats_update_begin(&stats->sync); stats->n_lost++; u64_stats_update_end(&stats->sync); return err; } ~~~ 上面是大概的分析了下upcall()函数,不过这不是本blog的重点,本blog重点是由dp_ifindex = get_dpifindex(dp);引出的一个框架问题,感觉有必要分析清楚(有这个价值),关系到网桥和端口之间的关系。 =================================================================================================================== 切入点还是从dp_ifindex = get_dpifindex(dp);开始吧,这是一个设备接口索引,就是获取网卡设备索引号的。 ~~~ static int get_dpifindex(struct datapath *dp) { struct vport *local; int ifindex; // rcu读锁 rcu_read_lock(); // 根据网桥和指定port_no查找vport结构体 local = ovs_vport_rcu(dp, OVSP_LOCAL); //get_ifindex:获取与所述设备相关联的系统的接口索引。这个可以参考net_device网络设备结构体 //可以为null,如果设备不具备的接口索引。 if (local) ifindex = local->ops->get_ifindex(local); else ifindex = 0; rcu_read_unlock(); return ifindex; } ~~~ 继续追查下去,发现最后会调用struct vport *ovs_lookup_vport(const struct datapath *dp, u16 port_no)函数来查询端口结构体。其实到这里你就会发现一些情况了。其中注意下各个函数的调用传的参数。 ~~~ struct vport *ovs_lookup_vport(const struct datapath *dp, u16 port_no) { struct vport *vport; struct hlist_head *head; // 这个调用了vport_hash_bucker()函数,具体实现在下面,这是一个查找hash表头部的函数 head = vport_hash_bucket(dp, port_no); // 上面是查找hash表头部,说明有多个hash表头,每个hash表头下面应该挂载了很多node节点 // 而下面就是Linux内核中定义的宏,用来遍历查找hash表中每个node节点的,通过匹配port_no来查找到vport // struct vport ; struct hlist_head head ; struct hlist_node hlist_for_each_entry_rcu(vport, head, dp_hash_node) { if (vport->port_no == port_no) return vport; } return NULL; } /*----------------------------------------------------------------------------------------------*/ // 这是查找哈希头函数,有多个相连的hash头链表 static struct hlist_head *vport_hash_bucket(const struct datapath *dp,</span> u16 port_no) { // port_no & (DP_VPORT_HASH_BUCKETS - 1)就是查找hash位置 // 比如表长为8的,需要查找id为10,那么用10/8 == 2。10 & (8-1) == 2 return &dp->ports[port_no & (DP_VPORT_HASH_BUCKETS - 1)]; } /*----------------------------------------------------------------------------------------------*/ // 下面是Linux中定义的宏,专门用来遍历链表中的节点的 // struct vport ; struct hlist_head head ; struct hlist_node // hlist_for_each_entry_rcu(vport, head, dp_hash_node) #define hlist_for_each_entry_rcu(pos, head, member) \ for (pos = hlist_entry_safe (rcu_dereference_raw(hlist_first_rcu(head)),\ typeof(*(pos)), member); \ pos; \ pos = hlist_entry_safe(rcu_dereference_raw(hlist_next_rcu(\ &(pos)->member)), typeof(*(pos)), member)) /*----------------------------------------------------------------------------------------------*/ // xxx(first, vport , datapath) 求vport的结构体 #define hlist_entry_safe(ptr, type, member) \ ({ typeof(ptr) ____ptr = (ptr); \ ____ptr ? hlist_entry(____ptr, type, member) : NULL; \ }) ~~~ 上面的遍历链表节点宏,是Linux专门定义的,个人感觉还是比较巧妙。在内核代码中有很多地方用到这个宏,hlist_entry_safe(xxxx)就是[linux内核之container_of()详解(即:list_entry()的详解)](http://blog.csdn.net/yuzhihui_no1/article/details/38356393)。 分析到这里看出什么问题来了没?就是网桥和端口连接关系问题。 第一、在struct vport *ovs_lookup_vport(const struct datapath *dp, u16 port_no)中调用了vport_hash_bucket(dp, port_no);来获取head结构体,这个函数非常简单,可以看到它里面的实现其实就一句话:&dp->ports[port_no & (DP_VPORT_HASH_BUCKETS - 1)];但这说明了一个问题,就是vport的head在个哈希表中。 第二、调用hlist_entry_safe()传的参数:hlist_entry_safe(head->first,vport,dp_hash_node),这可以看出dp_hash_node是连接head下面的,组成vport链表的。 所以综合上面情况,可以看出网桥和vport的连接结构为: ![](https://box.kancloud.cn/2016-02-17_56c42ec4ef46f.jpg) 上面图中vport结构体链表其实是用dp_hash_node链接起来的,所以说dp_hash_node是哈希链表链接元素,而其他则是数据结构体。为了形象点,所以没怎么区分,理解就行。 看到这个结构可能会有点诱惑:那么vport中的struct hlist_node hash_node;字段是干什么的。开始我也以为这个字段是连接vport形成vport链表的,而dp_hash_node是有关网桥的链表。但错了,虽然现在我也不能够非常清楚hash_node字段是干什么用的。可以查看下vport结构体各个字段解释: ~~~ /** * struct vport - one port within a datapath * @rcu: RCU callback head for deferred destruction. * @dp: Datapath to which this port belongs. * @upcall_portid: The Netlink port to use for packets received on this port that * miss the flow table. * @port_no: Index into @dp's @ports array. * @hash_node: Element in @dev_table hash table in vport.c. * @dp_hash_node: Element in @datapath->ports hash table in datapath.c. * @ops: Class structure. * @percpu_stats: Points to per-CPU statistics used and maintained by vport * @stats_lock: Protects @err_stats and @offset_stats. * @err_stats: Points to error statistics used and maintained by vport * @offset_stats: Added to actual statistics as a sop to compatibility with * XAPI for Citrix XenServer. Deprecated. */ ~~~ 我追查了下hash_node,确实发现和dev_table有关,但具体的还没有分析出来。dev_tables是什么?他的定义是: ~~~ /* Protected by RCU read lock for reading, ovs_mutex for writing. */ static struct hlist_head *dev_table; ~~~       可以看下他们相关联的函数:struct vport *ovs_vport_locate(struct net *net, const char *name),现在只找相关的东西,不会具体分析函数语句: ~~~ struct vport *ovs_vport_locate(struct net *net, const char *name) { struct hlist_head *bucket = hash_bucket(net, name); struct vport *vport; hlist_for_each_entry_rcu(vport, bucket, hash_node)// 这里可以看出vport结构体中的hash_node是和bucket一样的,那么bucket是什么呢? if (!strcmp(name, vport->ops->get_name(vport)) && net_eq(ovs_dp_get_net(vport->dp), net)) return vport; return NULL; } ~~~ 在追查bucket时,调用了hash_bucket()函数,可以看下实现: ~~~ static struct hlist_head *hash_bucket(struct net *net, const char *name) { unsigned int hash = jhash(name, strlen(name), (unsigned long) net);// 求随机数 return &dev_table[hash & (VPORT_HASH_BUCKETS - 1)];// 返回的是dev_table表中的某个元素的地址 } ~~~ 到这里就可以看出vport结构中的hash_node确实和dev_table有关,具体有什么关系就不再深究了,因为这不是科研。如果看了前面那副框架图的,在网桥连接vport的框架部分应该是上面的图了,当然这是个人观点。 转载请注明作者和原文出处,原文地址:[http://blog.csdn.net/yuzhihui_no1/article/details/41546481](http://blog.csdn.net/yuzhihui_no1/article/details/41546481) 若有不正确之处,望大家指正,共同学习!谢谢!!!