Open vSwitch中的datapath flow匹配过程

 

看OVS2.7的datapath表项匹配是一件很蛋疼的事情
  1. 数据结构看不懂
  2. 匹配算法经过了多次演进,已经有点复杂了,看代码完全看不懂,我能怎么办,我也很绝望啊!
 
2.1之前精确匹配时代,匹配过程是1.0
2.1的时候改成了megaflow匹配,匹配过程加入了mask,是为2.0
2.4的时候又对megaflow本身做了cache处理,是为3.0
 
在这个过程中,数据结构本身也发生了变化,庚志辉同时写的博客里的玩意在2.7里已经完全对不上号了
先贴一张2.7版本中目前的数据结构图
 

 
先说收包的流程:
  1. 在创建一个vxlan型的vport时,会调用到vxlan_create来创建这个vport,这个函数做了两件事情

    1. 调用vxlan_tnl_create,alloc要创建的vport,并创建好vxlan设备
    2. 调用ovs_netdev_link,把上一步创建的vxlan设备与vport绑定起来,并且还注册了一个收包回调函数是为netdev_frame_hook,如下:
  2. netdev_frame_hook收到包后,把包转交给netdev_vport_receive,但由于不知道这个宏USE_UPSTREAM_TUNNEL功能是什么,所以到底转交的时候带不带第二个参数就搞不清楚了,如下

    这里要注意的是,netdev_frame_hook及netdev_port_receive是标准的收包流程,所谓标准指的是,只要是发向OVS中port的包,无论port是什么类型,是VXLAN vport,还是netdev vport,还是gre vport,走的都是这个流程,这些vport类型的收包回调统一都是netdev_frame_hook
  3. 无论什么vport类型,包都会走netdev_frame_hook->netdev_port_receive这个流程,然而在netdev_port_receive中就要分流了,如下:

    在这个函数中,根据skb下挂的dev,找出该包所收包时所属于的vport,然后调用ovs_vport_receive
  4. 接下来到了ovs_vport_receive函数中,这个函数主要就做了一件事情:把包中的标识信息(一二三四层外加conntrack信息)扒到一个sw_flow_key结构中,这个结构并不复杂,字段也比较容易理解,这个sw_flow_key结构就是后续datapath转发表匹配时很关键的一个东西。把这件事做完后,就把skb本身与扒出来的这个sw_flow_key一起送给ovs_dp_process_packet函数中进行进一步处理
  5. ovs_dp_process_packet如下

    在这个函数中,将调用ovs_flow_tbl_lookup_stats()进行datapath转发表项的匹配工作
    第一个参数是查询的转发表,这个表挂在datapath下,类型为flow_table,看上面的数据结构图
    第二个参数是之前扒出来的sw_flow_key类型结构实例
    第三个参数是调用Linux kernel函数对skb进行的一个哈希数值
    第四个参数是一个出参,如果是megaflow匹配的话,这个出参将携带掩码匹配命中的字段数出来
  6. 至此,先停一下车,上面说的是包是怎么收进来的,接下来就要看包是怎么匹配的了,先停一下车
 
 
首先先说一下datapath转发表项匹配的概览,总共是分为三个层次的:
  1. 第一个层次,假设没有megaflow,即按位选择性匹配,datapath转发表项必须全12个字段精确匹配,你会怎么做?这种情况下,匹配就很简单,我们把包中的信息扒到sw_flow_key结构中,然后在OVS中设计一个哈希表,映射的就是[sw_flow_key <==> 如何转发],直接用这个哈希表按key查询,O(1)就能完成匹配。
    做个不恰当的比喻,内核datapath中实现了一个类似于std::map的数据结构,当然这是一个哈希表,其中键是sw_flow_key类型的,值就是“要执行的动作”(也就是sw_flow类型)
    当packet收上来时,直接把packet解压出来的key当成键,去查询它对应的“要执行的动作”。。查询成功,就走快转直接从内核转走。。查询失败,就是内核datapath转发表项匹配失败,给用户态上送upcall消息。用户态根据OpenFlow流表,走慢转,然后再把慢转的行为总结成datapath转发表里的“要执行的动作”,下发给内核,这样,下一次再来,就直接走快转了。

    但是问题出现了,精确匹配的最大缺点就是datapath转发表项数量会爆炸膨胀,你想一下用户态OpenFlow流表中的安全组的实现,conjunctive,你再想一想这些安全组映射成datapath转发表项,表项数量得炸成什么样。
    所以为了解决这个问题,就出现了megaflow式匹配,这个洋文名字的意思就是:选择性的匹配某些字段
  2. megaflow匹配就是第二个层次
    1. 入的包解压出来的sw_flow_key实例是所有字段都有的
    2. 匹配时只需要匹配部分的sw_flow_key中的字段,所以OVS设计了一个数据结构叫sw_flow_mask,sw_flow_mask中有两个很重要的字段:sw_flow_key_range与sw_flow_key,其中range就指定了匹配哪些字段。。

      比如,比如,举个不严谨的例子,datapath中要支持两种匹配:

      第一种:按MAC层src与dst及IP层src与dst的匹配
      第二种:IP层src与dst的匹配
      那么就需要做两个sw_flow_mask实例,假设macsrc/macdst/ipsrc/ipdst的编号分别是3、4、5、6,那么这两个sw_flow_mask的实例的示意大致如下:
      sw_flow_mask first = {
          range = [3,4]
          key = {
              ...
              macsrc=11:22:33:44:55:66
              macdst=66:55:44:33:22:11
              ...
          }
      }
      sw_flow_mask second = {
          range = [3,6]
          key = {
              ...
              macsrc=22:22:22:33:33:33
              macdst=33:33:33:22:22:22
              ipsrc=1.2.3.4
              ipdst=4.3.2.1
              ...
          }
      }
      然后这两个实例放在哪里呢?在(datapath.table)->mask_array中,在旧版本的OVS实现中,这些sw_flow_mask的实例被组织成链表,在2.7版本中,直接组织成顺序表了

      那么packet收上来是如何进行匹配的呢?
      首先,要对(datapath.table)->mask_array中的所有sw_flow_mask进行遍历
          然后,对于每个sw_flow_mask实例,这里称为mask,去比对mask.range范围内,mask.key中的值,
          如果这些值与packet解压出来的key一致,那么就表示megaflow匹配成功

      这个时候接下来怎么搞呢?和第一步一样,依然是要去查哈希表,查出一个sw_flow出来,但是哈希表是全字段匹配的呀。
      比如包原先解压出来的sw_flow_key是为key,这个时候用key作键去查哈希表,结果并不是我们希望得到的megaflow匹配结果,而是全匹配结果(很有可能会miss)
      所以不能用key去当键去查哈希表,而应该用匹配的sw_flow_mask实例里的"range + key"这两个信息结合起来,当成键,去查哈希表。

      在这一版的匹配流程中,相较于上一版,解决了datapath转发表项数量爆炸的问题。
      并且在实际实现中,对于sw_flow结构,还增加了一个用来表示mask的字段,是为sw_flow->mask,类型是为sw_flow_mask
  3. 上一版的匹配流程虽然改善了datapath转发表项的爆炸问题,但是又引入了一个新的问题(真他妈是聋子治成哑巴了):性能下降了!
    第一版虽然很浪费内存空间,但是查找快啊!只查一次哈希表就世界太平了!顶多桶位上链表长度长一点撑死了!
    第二版虽然节省了空间,但又浪费时间了啊!你看,先要遍历所有的mask_array中的sw_flow_mask实例,来看哪个实例与包的key能对上!

    所以OVS这拨人又处心积虑的搞出了第三版匹配流程。
    核心思想还是站在前人的工作上糊一层屎:既然遍历mask_array里的实例太浪费时间,那么就不要以“遍历”的形式去做这件事,来,老子的意大利散列函数呢??想办法干他娘的一炮!

    在上一版的策略中,流程大概是:
    第一层:遍历mask
    第二层:根据masked_key查询flow

    所以这一版的策略就改进了这一点,现在流程是这样的:

    第一层:根据packet的key,查询masked_key
    第二层:根据masked_key,查询flow

    注意这两个层次的查询都是哈希表(字典)查询,所以总共实现了两个哈希表(字典):
    第一个字典:键为packet的标识信息,值为mask的标识信息
    第二个字典:键为masked_key,值为flow

    大概逻辑就是这样,下面将讲第三版匹配流程的具体实现
 
 

 
现在回过头来看具体实现,注意配合数据结构图看
 
第一步:根据packet查询masked_key
第一个哈希表的实现就是图中红色的部分,其查询的键是为包的skb结构下的rxhash字段,rxhash字段由Linux内核中的jhash算法,根据包的三层源IP+目的IP+四层源端口+目的端口四个信息加工而成
rxhash字段共有32位,哈希查询的时候,从低八位开始取,每次取rxhash中的八位作为哈希桶的索引号,查询重复四次
这种哈希表的散列冲突处理办法类似于cuckoo哈希表,但不同的是,这里的实现是为每个键提供四个不同的散列值,而不是提供四个散列函数
第二步:根据masked_key查询flow
 
还没有看懂的点:
  1. ufid_ti的用途?
  2. sw_flow结构中,每个table_instance都对应着两个链表字段,为什么?
  3. table_instance结构中的node_ver是什么用途?
 

 

转载于:https://www.cnblogs.com/neooelric/p/6598545.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值