Open vSwitch中的datapath flow匹配過程

張浮生發表於2017-03-22

 

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

 

相關文章