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是什么用途?
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM