openVswitch(OVS)源代碼分析之工作流程(數據包處理)

  轉載請註明原文出處,原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/39378195

上篇分析到數據包的收發,這篇開始着手分析數據包的處理問題。在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用來對所有數據的一個統計作用:

[cpp] view plain copy
  1. // CPU對給定的數據包處理統計  
  2. struct dp_stats_percpu {  
  3.     u64 n_hit; // 匹配成功的數據包個數  
  4.     u64 n_missed; // 匹配失敗的數據包個數,n_hit + n_missed就是接受到的數據包總和  
  5.     u64 n_lost; // 丟失的數據包個數(可能是datapath隊列溢出導致)  
  6.     struct u64_stats_sync sync;  
  7. };  

        第二、是數據包發送到用戶空間的參數結構體,在匹配流表沒有成功時,數據將發送到用戶空間。而內核空間和用戶空間進行數據交互是通過netLinks來實現的,所以這個函數就是爲了實現netLink通信而設置的一些參數:

[cpp] view plain copy
  1. // 把數據包傳送給用戶空間所需的參數結構體  
  2. struct dp_upcall_info {  
  3.     u8 cmd; // 命令,OVS_PACKET_CMD_ *之一  
  4.     const struct sw_flow_key *key; // key值,不能爲空  
  5.     const struct nlattr *userdata; // 數據的大小,若爲空,OVS_PACKET_ATTR_USERDATA傳送到用戶空間  
  6.     u32 portid; // 發送數據包的Netlink的PID,其實就是netLink通信的id號  
  7. };  

        下面是來分析ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb);函數的實現源代碼:

[cpp] view plain copy
  1. // 數據包的處理函數,openVswitch的核心部分  
  2. void ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb)  
  3. {  
  4.     struct datapath *dp = p->dp; // 定義網橋變量,得到端口所在的網橋指針  
  5.     struct sw_flow *flow; // 流表  
  6.     struct dp_stats_percpu *stats; // cpu中對數據包的統計  
  7.     struct sw_flow_key key; // skb中提取到的key值  
  8.       
  9.           
  10.     u64 *stats_counter;  
  11.     int error;  
  12.      // 這應該是linux內核中的,openVswitch中很多是根據linux內核設計而來的  
  13.      // 這裏應該是對網橋中的數據包統計屬性進行初始化  
  14.     stats = this_cpu_ptr(dp->stats_percpu);  
  15.   
  16.      // 根據端口和skb數據包的相關值進行提取,然後封裝成key值  
  17.     error = ovs_flow_extract(skb, p->port_no, &key);  
  18.     if (unlikely(error)) { // 定義宏來判斷key值得提取封裝是否成功  
  19.         kfree_skb(skb);// 如果沒有成功,則銷燬掉skb數據包,然後直接退出  
  20.         return;  
  21.     }  
  22.   
  23.      // 調用函數根據key值對流表中所有流表項進行匹配,把結果返回到flow中  
  24.     flow = ovs_flow_lookup(rcu_dereference(dp->table), &key);  
  25.     if (unlikely(!flow)) { // 定義宏判斷是否匹配到相應的流表項,如沒有,執行下面代碼  
  26.         struct dp_upcall_info upcall; // 定義一個結構體,設置相應的值,然後把數據包發送到用戶空間  
  27.         // 下面是根據dp_upcall_info數據結構,對其成員進行填充  
  28.         upcall.cmd = OVS_PACKET_CMD_MISS;// 命令  
  29.         upcall.key = &key;// key值  
  30.         upcall.userdata = NULL;// 數據長度  
  31.         upcall.portid = p->upcall_portid;// netLink通信時的id號  
  32.         ovs_dp_upcall(dp, skb, &upcall);// 把數據包發送到用戶空間  
  33.         consume_skb(skb);// 銷燬skb  
  34.         stats_counter = &stats->n_missed;// 用未匹配到流表項的包數,給計數器賦值  
  35.         goto out;// goto語句,內部跳轉,跳轉到out處  
  36.     }  
  37.        // 在分析下面的代碼時,先看下OVS_CB()這個宏:#define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb)  
  38.        // 這個宏如果知道skb數據結構的話,就好理解。大概的意思是把skb中保存的當前層協議信息的數據強轉爲ovs_skb_cb*數據指針  
  39.     OVS_CB(skb)->flow = flow;// 能夠執行到這裏,說明匹配到了流表。把匹配到的流表想flow賦值給結構體中成員  
  40.     OVS_CB(skb)->pkt_key = &key;// 同上,把相應的key值賦值到結構體變量中  
  41.       //  這是匹配成功的,用匹配成功的數據包數賦值於計數器變量  
  42.     stats_counter = &stats->n_hit;  
  43.     ovs_flow_used(OVS_CB(skb)->flow, skb);// 調用函數調整流表項成員變量(也許是用來流表項的更新)  
  44.     ovs_execute_actions(dp, skb); // 根據匹配到的流表項(已經在skb中的cb)執行相應的action操作  
  45.   
  46. out:   
  47.      // 這是流表匹配失敗,數據包發到用戶空間後,跳轉到該處,  
  48.      // 對處理過的數據包數進行調整(雖然沒匹配到流表,但也算是處理掉了一個數據包,所以計數器變量應該增加1)  
  49.     u64_stats_update_begin(&stats->sync);  
  50.     (*stats_counter)++;  
  51.     u64_stats_update_end(&stats->sync);  
  52. }  
        上面就是openVswitch的核心部分,所有的數據包都要經過此函數進行邏輯處理。這只是一個邏輯處理的大體框架,還有一些細節(key值得提取,流表的匹配查詢,數據傳輸到用戶空間,根據流表執行相應action)將在後面分析。當把整個openVswitch的工作流程梳理清晰,會發現這其實就是openVswitch的頭腦部分,所有的邏輯處理都在裏實現,所以我們自己添加代碼時,這裏往往也是個不錯的選擇。

        如果看了前面那篇openVswitch(OVS)源代碼分析之工作流程(收發數據包),那麼應該記得其中也說到了可以在收發函數中添加自己代碼,因爲一般來說收發函數也是數據包的必經之地(發送函數可能不是)。那麼怎麼區分在哪裏添加自己代碼合適呢?

        其實在接受數據包函數中添加自己代碼和在這裏的邏輯處理函數中添加自己代碼,沒有多大區別,因爲接受函數中沒有做什麼處理就把數據包直接發送打邏輯處理函數中去了,所以這兩個地方添加自己代碼其實是沒什麼區別的。但是從習慣和規範來說,數據包接受函數只是根據條件控制數據包的接受,並不對數據包進行邏輯上的處理,也不會對數據包進行修改等操作。而邏輯處理函數是會對數據包進行某些邏輯上的處理。(最明顯的是修改數據包內的數據,一般來說接受數據包函數中是不會對數據包內容修改的,但邏輯處理函數則有可能會去修改的)。

        而在數據包發送函數中添加自己代碼和邏輯函數中添加自己代碼也有些區別,數據包發送函數其性質和接受函數一樣,一般不會去修改數據包,而僅僅是根據條件判斷該數據包是否發送而已。

        那下面就邏輯處理函數中添加代碼來舉例:

        假若要把某個指定的IP主機上發來的ARP數據包進行處理,把所有的請求數據包變成應答數據包,原路返回。這裏最好就是把自己的代碼添加到邏輯處理函數中去(如果你要強制的添加到數據包接受函數中去也可以),因爲這裏要修改數據包的內容,是一個邏輯處理。具體實現:可以在key值提取前對數據包進行判斷,看是否是ARP數據包,並且是否是指定IP主機發來的。若不是,交給系統去處理;若是,則對Mac地址和IP地址進行交換,並且把請求標識變成應答標識;最後調用發送函數從原來的端口直接發送出去。這只是一個簡單的應用,旨在說明邏輯處理代碼最好添加到邏輯處理函數中去。如果要處理複雜的操作也是可以的,比如定義自己的流表,然後然後屏蔽掉系統的流表查詢,按自己的流表來操作。這就是一個對openVswitch比較大的改造了,流表、action、流表匹配等這些openVswitch主要功能和結構都要自己去定義實現完成。

        以上分析得就是openVswitch的核心部分,當然了只是一個大體框架而已,後續將會逐步完善。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章