Open vSwitch匹配處理流程和拓展性
目前群裡很多同學做ovs研究,也有很多人來討論如何自定義OVS匹配域的問題,所以今天的分享主題就圍繞OVS匹配處理流程和拓展性展開,這和之前SDNLAB上發的自定義action,可稱為姊妹篇。它們是去年研讀OVS原始碼時候的一些收穫和心得,今天拿出來和大家分享。
由於拓展匹配域更貼近OVS開發實踐,難免會提到程式碼部分。但為了簡潔明瞭,此次分享主要遵循兩個目的:講清楚其大體邏輯、然後點明需要原始碼新增的地方,提高匹配域拓展成功率。
一、整體思路
現在進入正題,今天的分享從三個方面進行:
- 匹配域相關的各個模組簡單分析。
- 安裝檢驗和除錯
- 演示結果。
相比在ovs原始碼中新增自定義action,自定義匹配域顯得關係更為複雜凌亂一些。為了讓和匹配域相關的模組條理更加清楚明瞭,我儘量將要提到的相關模組關係化,防止漏掉和匹配域相關的部分。這裡先給出總體架構圖:
架構圖中包含了將要分析的8大模組,每一個裡面都有和匹配相關的內容,接下來會按照這個思路逐一分析。其實大家發現,這和流表從控制器下發後,資料包進入交換機的處理流程非常吻合,想必大家多多少少有一些認識。
二、各個模組分析
下來進入分享的重點。按照圖中給出的思路,各個模組講解順序依次為:
1、匹配域定義
2、flowmod解析
3、使用者層表項插入
4、核心層packet解析和匹配處理
5、Upcall接收和分類
6、使用者層查詢匹配處理
7、表項和packet的下發操作
8、核心層flow插入和packet執行
9、其他
1、匹配域定義
Ovs匹配域是基於OpenFlow協議的,因此,如果要新增一個新的匹配域,需要延續OF協議定義一個匹配域的邏輯,這樣拓展出的新匹配才能較為容易的和其他OF已經定義的匹配域相容起來,同時保障OVS的匹配處理邏輯不發生改變。
1)目前OF支援兩種定義匹配域的格式,用的較多的是OXM格式,即TLV格式(型別,長度和值)。我們之後的講解以TLV格式為基礎進行。那麼要想實現一個新的匹配域,代表型別的T和長度的L比不少,他們定義在列舉型別和巨集定義中。
首先看列舉型別,目前OF在1.3協議中已經定義了40種匹配欄位,它們列舉值定義在include\openflow\Openflow-1.2.h中,部分截圖如下:
每一個匹配域有相應ENUM值,從in_port的0到IPV6_EXTHDR的39,因此對於新的匹配域,需要以這種格式進行新增即可,但ENUM值必須是目前還沒有定義過的值。
2)除了要新增列舉值外,還需要新增一個TLV相關的巨集定義。TLV頭部如下(TL部分,相當於綁定了一個匹配欄位的型別和長度):
對於一個新匹配域,只需要按照上面格式進行新增即可,注意4或是8指的是TLV中的L數值,表示匹配域值的長度。如對於inport則是4位元組。之後OVS對flowmod中匹配域解析就全依賴這個列舉值和巨集定義了,此外提一句,如果是在控制端也做匹配域新增,需要和這個列舉值和TL格式對應起來。
2、FlowMod訊息解析
完成之前的新欄位的TLV定義還遠遠不夠,即將等待我們的是,OVS如何能夠從Flowmod訊息中準確提取出匹配域,並且能無排斥的插入原生的OVS流表中。接下來分析一下flowmod訊息解析模組。
先上圖:
圖體現了大體思路: Flowmod訊息的匹配域部分,最終是要按照TLV格式逐一解析出來,然後經過一系列依賴性和重複性檢測等,最後才能將匹配域部分完整的解析放置在match結構體中。
Match是什麼?是用來裝載從flowmod訊息中解析出來的匹配域。先來看看match結構體:
Match包含了flow和wc,前者裝載欄位值,後者標記欄位掩碼(深入會發現wc也是用flow結構體儲存掩碼)。Flow結構體包含了匹配域所有欄位型別,因此對於新的欄位,需要在此結構體中新增。
需要注意的是,匹配欄位在flow中新增的前後位置要固定,因為後面新增相應原始碼時需要和這個位置一致。
2)說完了match,那如何從flowmod的匹配域中逐一解析出每一個欄位呢?(其主要思想體現在函式nx_pull_raw()中)
大體是這樣的,匹配域由多個TLV組成,每一個TLV是一個匹配欄位。則OVS先會從flowmod匹配域中按照TLV中的L將每個OXM(TLV格式)切割出來。這樣是不是就解析完了呢,顯然不是,因為切割後的合法性無法保障(如長度是否符合定義,各個欄位依賴是否正確等)。
這裡就需要後面的工作了,通過分割出來的OXM的header(即TL部分),在匹配域雜湊表mf_field(Hmap)中做雜湊查詢,然後查詢到這個TL應該對應的mf_field結構體。mf_field是OVS已經宣告定義好的匹配域資訊集合,包含依賴性,名字,長度等資訊,這些可以對分割出來的該欄位進行檢驗。Ok,清楚了這些,下面給出匹配域欄位解析的示意圖:
剛才提到欄位資訊的集合mf_field,其以陣列形式定義在mf_fields中,我們需要在此處寫入新欄位的資訊:
如上面這個是inport欄位資訊集合,可以看到它包含了名字,欄位長度和最開始提到的匹配域定義的enum OXM_OF_IN_PORT。這裡注意,包含的第一個屬性是mf_field的id號,一個mf_field有一個id,其定義在mf_field_id列舉型別中(對於新欄位也需要在這裡新增一個id,注意相對位置)。這個id號算是OVS自身識別匹配域型別的方式,之後匹配域合法性檢測會都會用到這個id號。
3)接下來,會根據欄位mf_field資訊對分割的每個欄位做依賴性檢測、重複性檢測和匹配域值的有效性檢測等。
A、依賴性檢測:如當設定ipv4匹配欄位時,會檢測match->flow的“二層協議匹配欄位”是否已經是ip協議。如果新新增匹配欄位有依賴性限制,則需要在函式mf_are_prereqs_ok中新增case進行檢測。
B、重複性檢測:因為匹配域欄位是逐個解析的,為了防止當前欄位型別已經在之前存在過,則需要進行重複性檢測,對於新的欄位,需要在函式mf_is_all_wild()新增程式碼進行檢測。
C、匹配域值的有效性檢測:對於一些匹配欄位值是有規定的,如inport號是否大於最大範圍等,對於新欄位也需要在函式mf_is_value_valid()中完成檢測。
檢測完就可以安安心心的將解析的每個欄位值賦給match結構體了,賦值時會分有掩碼和無掩碼情況,也需要新增相應新欄位原始碼。
其實,令人欣慰的是,對於一個新欄位需要在各處新增原始碼,看似繁雜,也基本就是照別的欄位原始碼格式多寫一個case的事情,照貓畫虎也算是是個好方法。
3、流表項插入
完成flowmod的匹配域解析,那麼剩下的就是依照flowmod要求進行流表項刪除、新增等操作,這裡對於一個新欄位無需原始碼改動。
OVS有很多保障效能的方法,這裡就有一處,簡答提一下:Ovs定義了一個重要結構體cls_rule,其與匹配域資訊、priority資訊等相關,且cls_rule關聯一個相應的流表項。當ovs向流表中插入新表項時,不是以表項全部內容進行重複性檢測,而是通過cls_rule在分類器cls_calssifier中進行查詢,這種對流表項分類查詢方法可以大大提高工作效率,完成新表項的新增或是更新。
4、核心層packet解析和匹配處理
使用者層表項解析與插入告一段落,下來就是當資料包進入交換機時,如何完成packet解析與匹配處理。(核心程式碼位於datapath資料夾下,資料包頭解析和匹配旅程從ovs_vport_receive()開始)
我們知道,ovs為了提高效率,資料包會先在核心層datapath進行流表項匹配處理,對於匹配失敗,或者是匹配到表項的action為發向使用者層時,才會去使用者層繼續查詢匹配。對於在使用者層匹配成功的資料包會按照表項action相應處理,並向核心層下發一條匹配到的表項,方便以後類似資料包直接在核心層完成匹配轉發。
這個過程將是要一一解釋的關鍵點,無不和匹配域息息相關。先來說說資料包進入ovs核心層的處理過程。
1)當一個OVS埠接收到一個數據包,不是將整個資料包在核心層的流表中匹配查詢,這樣效率低下,而是需要對此資料包頭欄位進行解析,將解析出來的各個匹配欄位值和埠號一起構造成查詢key,然後用key在流表中進行匹配查詢。
查詢key,它是一個sw_flow_key結構體,如下,包含了各個匹配欄位的型別,對於新欄位也需要在這裡進行新增。
此外,需要呼叫函式key_extract()依次從包頭中提取各個欄位放入key中。如果你構造了一個數據包新協議欄位,就需要在這個函式中提取相應包頭欄位賦值給key即可,包頭提取都是對linux的結構體操作,很方便快捷。
2)有了key,那就是核心層流表項匹配查詢的事情了。由於此次分享圍繞匹配域展開,核心中流表匹配查詢階段,不涉及具體的匹配欄位,也無需做修改新增,因此不具體分析匹配查詢流表項的具體過程。
查詢結果無非兩種,查詢成功和查詢失敗。查詢失敗則構造upcall上交使用者層繼續查詢處理,但這裡需要注意,即使查詢到也可能面臨上交處理。因為有一些action無法在核心層執行,這種action在下發到核心層時已經標記為OVS_ACTION_ATTR_USERSPACE型別,此時也需要上交使用者層進一步匹配處理。
上交使用者層時(主要體現在queue_userspace_packet函式中),會構造上交的資料包user_skb(skb_buf結構體),然後通過generic netlink通訊機制上交給使用者層。
結構體skb_buf可以簡單理解為這樣的結構:Netlink頭部+Attr+Attr+...,Attr是type+len+data結構。Attr主要分為三個型別資訊:
- key:即由包頭等構造的查詢key,必不可少,資料型別type為OVS_PACKET_ATTR_KEY
- userdata:用於匹配成功卻仍要走slow-path的資料包,標記了action引數(如原因),資料型別type為OVS_PACKET_ATTR_USERDATA
- packet:顧名思義,原始資料包。型別type為OVS_PACKET_ATTR_PACKET。
注意,在這裡,有兩部分內容和匹配域相關,新增新匹配域時候就需要在此處修改原始碼:
A是對於待上交key中含有的各個欄位計算總長度(key_attr_size())
B上傳資料user_skb中的key包含很多匹配欄位。因此新欄位也要從key中提取出來加入到待傳輸到使用者層的資料體中(函式ovs_nla_put_flow()),提取時會用到各個匹配域資料的型別(enum ovs_key_attr列舉型別中定義)。
5、Upcall接收和分類
到這裡,已經完成和匹配域相關的多大半內容,思路已經比較清晰,後面將加快進度。
上面說到,核心層會封裝含有key、packet和action引數等內容的upcall訊息上交使用者層。那麼使用者層接收到upcall之後直接匹配表項即可,為什麼還要分類呢?(其主要體現在函式read_upcalls()(ofproto-dpif-upcalls.c))。
先給一張圖:
可以看到,使用者層的upcall結構體有dupcall和miss兩個成員,這就和ovs效能提升密切相關了。OVS將具有相同key的upcall歸為一類,管理對映到同一個miss中。這樣就完成了相似packet的分類工作,便於後期統一匹配處理,提高效率。
在上面這個過程中,需要從key提取出flow進行雜湊查詢和分類。Flow就是前面講解到的使用者層用於表示匹配域的結構體,OVS呼叫函式flow_extract()函式從packet與md(metadata元資料)中解析並構造flow賦值給miss->flow,在這裡別忘了新增相應解析函式。
其實,分類還包括了對slow path原因的分類處理,因和匹配域無關,就不詳述了
6、使用者層查詢匹配處理
完成upcall前期接收和分類工作,下來就是匹配處理了(主要體現在函式handle_upcalls()(ofproto-dpif-upcall.c))。
這裡只有一處和新匹配域新增相關(odp_flow_key_from_flow__()函式),因此主要強調其工作原理。OVS會先分批(之前提到的,劃分為同一個miss的資料包)完成使用者層流表匹配查詢,然後得到流表項action,並將使用者層action翻譯為核心層odp_action,並對屬於slow_path的action資料包做特殊標記處理(miss.xout->slow),尤其對部分slow_path中slow_action的做help標記。之後就可以下發查詢到的表項到核心層了,並將資料包發到核心層去執行流表項的action。
這個過程很合情合理,但標記做什麼用呢?因為資料包匹配到的流表項,其action執行只能通過慢通道處理(最典型的就是Controller action,甚至是因為action過多或是資料量太大),因此標記後,就會將這些含有slow_path action的表項和packet 直接在使用者層完成特殊處理,這基本和核心層關係就不大了,效率自然也不會高。
7、表項和Packet的下發操作
接下來的工作,就是將表項下發到核心層,並將packet通過netlink機制下發到核心層去執行action(主要體現在函式dpif_operate()中)。
由於之前提到的slow-path原因,OVS會採用兩種形式下發,一種是和slow-path無關的統一處理下發,一種是和slow-path相關的單獨特殊處理。
1)統一下發處理較為簡單,就是批量以廣播形式通過netlink機制下發到核心層,完成流表項在核心層的安裝和packet在核心層action的執行。這裡需要注意的是,如果自定義的新匹配域屬於metadata型別,如inport這種,那麼需要在odp_key_from_pkt_metadata()函式中,實現將元資料內容的取出放入request快取後等待下發的功能。
2)特殊處理:對於一個需要slow-path處理的packet,其所有動作actions本應在使用者層執行(即在odp_execute_actions__()函式),但是執行到OVS_ACTION_ATTR_OUTPUT型別action時,不言而喻其最後需要傳送到核心層完成轉發。那麼這種含有slow_path的流表項是否需要下發到核心層?還記得之前的action翻譯嗎,這種表項會將action翻譯為OVS_ACTION_ATTR_USERSPACE下發到核心層中。如下,使用者層表項到核心層表項:
請注意,特殊處理中如果牽扯到set_field action,就需要在odp_execute_set_actio()新增新匹配域的set函式。
8、核心層flow插入和packet執行
轉了一圈,又回到了核心層。在核心層完成flow的插入和packet action執行工作基本就大功告成了。這裡面的原理比較簡單,因此只提及在表項插入過程中與匹配域相關的地方。
OVS主要在使用者層下發的表項資料中,對含有的匹配欄位值進行解析和欄位有效性檢驗,完成表項插入。匹配欄位解析中包含欄位長度解析(ovs_key_lens()函式)和欄位掩碼解析(ovs_key_from_nlattrs()函式),有效性檢驗(match_validate()函式)主要完成了匹配欄位是否全初始化檢驗、掩碼和值的一致性檢驗等,對於新匹配域,以上幾個函式需要修改。
9、其他
圍繞著OVS匹配域有關的處理流程,終於分析完了從表項解析、插入、匹配,執行等一系列過程。當然,新的匹配域可能還不能很好的運作,因為還差列印顯示和手動插入等功能。這部分比較獨立,簡單提及函式即可。
列印密切相關:
miniflow_extract()
flow_format()
odp_flow_key_attr_len()
ovs_key_attr_to_string()
format_odp_key_attr()
其他一些:
mf_set_flow_value()(lib/meta-flow.c)
mf_get_value()(lib/meta-flow.c)
nx_put_raw()(nx-match.c)
parse_odp_key_mask_attr函式()(lib\odp-util.c)
序號數FLOW_WC_SEQ
二、安裝檢驗和除錯
對於新增一個自定義匹配域,原始碼修改就算完成了,雖然比較繁瑣,但是每一處改動不大,基本照貓畫虎即可。安裝過程簡單,採用常規安裝OVS的方法即可。
如果安裝後,採用ovs-ofctl命令可以正常新增一條帶有自定義匹配域的流表項,並且資料包可以成功如願以償的匹配到這條表項,那基本就大功告成了。
如果安裝失敗或是匹配不能按照預想的效果,需要進行除錯。除錯一般採用兩種方法,檢視log資訊和gdb工具除錯:
1)log資訊:匹配域的新增涉及使用者層和核心層,ovs在使用者層提供了相應log函式VLOG_WARN、VLOG_INFO、VLOG_DBG等,直接使用即可,使用者層log資訊一般位於/usr/local/var/log/openvswitch/ovs-vswitchd.log中檢視;
核心層可以使用printk等函式新增log,並在/var/log/kern.log中檢視即可。
2)採用dbg方式,比較準確高階的。
三、演示結果
說了這麼多,沒有實驗結果都是不可靠的。因此,我下發了一條流表項,包含了一個新的匹配域,並且成功匹配到了資料包,達到預期效果。可以用ofctl檢視使用者層表項,用dpctl檢視核心層表項。
為了能夠正確新增自定義匹配域,上文對於ovs匹配欄位的執行流程和基本原理做了分析說明,全部下來,也算是勞力勞神,辛苦大家了。不過,希望此次分享能有助於群友在OVS上的二次開發。
技術有限,講解有誤的地方歡迎指正,拍磚~,最後,再次感謝SDNLAB和大家的支援。
Q&A
Q1:如何保證壓力下的效能
A1:ovs本身在查詢匹配上下了功夫,現在ovs也支援dpdk,效能會有所提升。不過這和硬體交換機比起來,還是有較大的提升空間。
Q2:對於新增匹配型別,只需要在使用者態修改,核心態不用修改?
A2:需要修改的,第四點中講到了核心層資料包頭解析和key修改等,都是核心態需要修改完成的。
Q3:請問你們現在ovs用在哪個方面的產品上?
A3:我們目前FNL實驗室利用OVS和CNVP等搭建了SDN實驗平臺,提供給學生切虛網做實驗。
Q4:核心態裡面是精準匹配,使用者態模糊匹配?
A4:核心態可以理解為“精確表項”。舉個例子,比如使用者層寫某條表項action是Flood,那麼給核心層下發的表項Action就是Output1,2,3..等等埠,核心層的action是使用者層翻譯過的。核心態具有提升效能等作用,這裡就是之前提到的資料包匹配處理的slow-path和fast-path問題了。
Q5:你的意思是完全沒有核心態應該也行吧?
A5:恩,記得安裝時候,可以不構建安裝核心態,即只跑使用者態,但功能上會有缺失~,也其實是很少這麼幹的。
Q6:一個網路包第一次進入這個交換機後,要麼被drop,要麼被轉發到控制器,核心是怎麼判斷的? 是轉發到控制器還是drop?
A6:核心的判斷全來自於使用者層曾經的判斷:一個網路包如果在核心層被匹配到,那就按照action執行,沒有匹配到,就上傳使用者層進行使用者層的匹配。使用者層會將匹配結果和相應表項下發到核心層,便於以後類似資料包在核心層匹配可以直接匹配成功,提高效率。至於是否轉發給控制器,這要看是否匹配到的表項action為Controller(of1.3)。
Q7:如果使用者層沒有匹配成功,使用者層會下發流表為drop?
A7:在of1.3中會的,不過核心層表項生存時間很短的哦,防止後來使用者向用戶態插入新表項導致匹配可以成功了。當然,生存時間短也是一種節約資源的方式。
Q8:你的意思是,核心沒有匹配成功,一定會發到使用者態,使用者態沒有匹配成功會有兩個動作1)轉發到控制器,2)直接drop?
A8:在of1.3中,使用者態如果匹配沒有成功,資料包會drop,且給核心層發一個精確表項(action為drop),在of1.3之前,使用者層沒有匹配到,才會預設發給控制器的。
Q9:如果你做出去這麼一套東西給客戶,然後跑起來了,使用者說斷了或者網速慢,但是使用者不會程式設計,怎麼排除故障?
A9:個人覺得,SDN中對於故障解決問題還不成熟,當然對於控制器端故障,可以通過叢集等方式解決,如果是資料層面,可以通過控制器端寫業務應用來檢測切換。
-----------------------------------------------------------------------------------------------------