一、整體思路
現(xiàn)在進(jìn)入正題,今天的分享從三個方面進(jìn)行:
匹配域相關(guān)的各個模塊簡單分析。
安裝檢驗和調(diào)試
演示結(jié)果。
相比在ovs源碼中添加自定義action,自定義匹配域顯得關(guān)系更為復(fù)雜凌亂一些。為了讓和匹配域相關(guān)的模塊條理更加清楚明了,我盡量將要提到的相關(guān)模塊關(guān)系化,防止漏掉和匹配域相關(guān)的部分。這里先給出總體架構(gòu)圖:
架構(gòu)圖中包含了將要分析的8大模塊,每一個里面都有和匹配相關(guān)的內(nèi)容,接下來會按照這個思路逐一分析。其實大家發(fā)現(xiàn),這和流表從控制器下發(fā)后,數(shù)據(jù)包進(jìn)入交換機的處理流程非常吻合,想必大家多多少少有一些認(rèn)識。
二、各個模塊分析
下來進(jìn)入分享的重點。按照圖中給出的思路,各個模塊講解順序依次為:
1、匹配域定義
2、flowmod解析
3、用戶層表項插入
4、內(nèi)核層packet解析和匹配處理
5、Upcall接收和分類
6、用戶層查找匹配處理
7、表項和packet的下發(fā)操作
8、內(nèi)核層flow插入和packet執(zhí)行
9、其他
1、匹配域定義
Ovs匹配域是基于OpenFlow協(xié)議的,因此,如果要添加一個新的匹配域,需要延續(xù)OF協(xié)議定義一個匹配域的邏輯,這樣拓展出的新匹配才能較為容易的和其他OF已經(jīng)定義的匹配域兼容起來,同時保障OVS的匹配處理邏輯不發(fā)生改變。
1)目前OF支持兩種定義匹配域的格式,用的較多的是OXM格式,即TLV格式(類型,長度和值)。我們之后的講解以TLV格式為基礎(chǔ)進(jìn)行。那么要想實現(xiàn)一個新的匹配域,代表類型的T和長度的L比不少,他們定義在枚舉類型和宏定義中。
首先看枚舉類型,目前OF在1.3協(xié)議中已經(jīng)定義了40種匹配字段,它們枚舉值定義在include\\openflow\\Openflow-1.2.h中,部分截圖如下:
每一個匹配域有相應(yīng)ENUM值,從in_port的0到IPV6_EXTHDR的39,因此對于新的匹配域,需要以這種格式進(jìn)行添加即可,但ENUM值必須是目前還沒有定義過的值。
2)除了要添加枚舉值外,還需要添加一個TLV相關(guān)的宏定義。TLV頭部如下(TL部分,相當(dāng)于綁定了一個匹配字段的類型和長度):
對于一個新匹配域,只需要按照上面格式進(jìn)行添加即可,注意4或是8指的是TLV中的L數(shù)值,表示匹配域值的長度。如對于inport則是4字節(jié)。之后OVS對flowmod中匹配域解析就全依賴這個枚舉值和宏定義了,此外提一句,如果是在控制端也做匹配域添加,需要和這個枚舉值和TL格式對應(yīng)起來。
2、FlowMod消息解析
完成之前的新字段的TLV定義還遠(yuǎn)遠(yuǎn)不夠,即將等待我們的是,OVS如何能夠從Flowmod消息中準(zhǔn)確提取出匹配域,并且能無排斥的插入原生的OVS流表中。接下來分析一下flowmod消息解析模塊。
先上圖:
圖體現(xiàn)了大體思路: Flowmod消息的匹配域部分,最終是要按照TLV格式逐一解析出來,然后經(jīng)過一系列依賴性和重復(fù)性檢測等,最后才能將匹配域部分完整的解析放置在match結(jié)構(gòu)體中。
Match是什么?是用來裝載從flowmod消息中解析出來的匹配域。先來看看match結(jié)構(gòu)體:
Match包含了flow和wc,前者裝載字段值,后者標(biāo)記字段掩碼(深入會發(fā)現(xiàn)wc也是用flow結(jié)構(gòu)體存儲掩碼)。Flow結(jié)構(gòu)體包含了匹配域所有字段類型,因此對于新的字段,需要在此結(jié)構(gòu)體中添加。
需要注意的是,匹配字段在flow中添加的前后位置要固定,因為后面添加相應(yīng)源碼時需要和這個位置一致。
2)說完了match,那如何從flowmod的匹配域中逐一解析出每一個字段呢?(其主要思想體現(xiàn)在函數(shù)nx_pull_raw()中)
大體是這樣的,匹配域由多個TLV組成,每一個TLV是一個匹配字段。則OVS先會從flowmod匹配域中按照TLV中的L將每個OXM(TLV格式)切割出來。這樣是不是就解析完了呢,顯然不是,因為切割后的合法性無法保障(如長度是否符合定義,各個字段依賴是否正確等)。
這里就需要后面的工作了,通過分割出來的OXM的header(即TL部分),在匹配域哈希表mf_field(Hmap)中做哈希查找,然后查找到這個TL應(yīng)該對應(yīng)的mf_field結(jié)構(gòu)體。mf_field是OVS已經(jīng)聲明定義好的匹配域信息集合,包含依賴性,名字,長度等信息,這些可以對分割出來的該字段進(jìn)行檢驗。Ok,清楚了這些,下面給出匹配域字段解析的示意圖:
剛才提到字段信息的集合mf_field,其以數(shù)組形式定義在mf_fields中,我們需要在此處寫入新字段的信息:
如上面這個是inport字段信息集合,可以看到它包含了名字,字段長度和最開始提到的匹配域定義的enum OXM_OF_IN_PORT。這里注意,包含的第一個屬性是mf_field的id號,一個mf_field有一個id,其定義在mf_field_id枚舉類型中(對于新字段也需要在這里添加一個id,注意相對位置)。這個id號算是OVS自身識別匹配域類型的方式,之后匹配域合法性檢測會都會用到這個id號。
3)接下來,會根據(jù)字段mf_field信息對分割的每個字段做依賴性檢測、重復(fù)性檢測和匹配域值的有效性檢測等。
A、依賴性檢測:如當(dāng)設(shè)置ipv4匹配字段時,會檢測match->flow的“二層協(xié)議匹配字段”是否已經(jīng)是ip協(xié)議。如果新添加匹配字段有依賴性限制,則需要在函數(shù)mf_are_prereqs_ok中添加case進(jìn)行檢測。
B、重復(fù)性檢測:因為匹配域字段是逐個解析的,為了防止當(dāng)前字段類型已經(jīng)在之前存在過,則需要進(jìn)行重復(fù)性檢測,對于新的字段,需要在函數(shù)mf_is_all_wild()添加代碼進(jìn)行檢測。
C、匹配域值的有效性檢測:對于一些匹配字段值是有規(guī)定的,如inport號是否大于最大范圍等,對于新字段也需要在函數(shù)mf_is_value_valid()中完成檢測。
檢測完就可以安安心心的將解析的每個字段值賦給match結(jié)構(gòu)體了,賦值時會分有掩碼和無掩碼情況,也需要添加相應(yīng)新字段源碼。
其實,令人欣慰的是,對于一個新字段需要在各處添加源碼,看似繁雜,也基本就是照別的字段源碼格式多寫一個case的事情,照貓畫虎也算是是個好方法。
3、流表項插入
完成flowmod的匹配域解析,那么剩下的就是依照flowmod要求進(jìn)行流表項刪除、添加等操作,這里對于一個新字段無需源碼改動。
OVS有很多保障性能的方法,這里就有一處,簡答提一下:Ovs定義了一個重要結(jié)構(gòu)體cls_rule,其與匹配域信息、priority信息等相關(guān),且cls_rule關(guān)聯(lián)一個相應(yīng)的流表項。當(dāng)ovs向流表中插入新表項時,不是以表項全部內(nèi)容進(jìn)行重復(fù)性檢測,而是通過cls_rule在分類器cls_calssifier中進(jìn)行查找,這種對流表項分類查找方法可以大大提高工作效率,完成新表項的添加或是更新。
4、內(nèi)核層packet解析和匹配處理
用戶層表項解析與插入告一段落,下來就是當(dāng)數(shù)據(jù)包進(jìn)入交換機時,如何完成packet解析與匹配處理。(核心代碼位于datapath文件夾下,數(shù)據(jù)包頭解析和匹配旅程從ovs_vport_receive()開始)
我們知道,ovs為了提高效率,數(shù)據(jù)包會先在內(nèi)核層datapath進(jìn)行流表項匹配處理,對于匹配失敗,或者是匹配到表項的action為發(fā)向用戶層時,才會去用戶層繼續(xù)查找匹配。對于在用戶層匹配成功的數(shù)據(jù)包會按照表項action相應(yīng)處理,并向內(nèi)核層下發(fā)一條匹配到的表項,方便以后類似數(shù)據(jù)包直接在內(nèi)核層完成匹配轉(zhuǎn)發(fā)。
這個過程將是要一一解釋的關(guān)鍵點,無不和匹配域息息相關(guān)。先來說說數(shù)據(jù)包進(jìn)入ovs內(nèi)核層的處理過程。
1)當(dāng)一個OVS端口接收到一個數(shù)據(jù)包,不是將整個數(shù)據(jù)包在內(nèi)核層的流表中匹配查找,這樣效率低下,而是需要對此數(shù)據(jù)包頭字段進(jìn)行解析,將解析出來的各個匹配字段值和端口號一起構(gòu)造成查詢key,然后用key在流表中進(jìn)行匹配查找。
查詢key,它是一個sw_flow_key結(jié)構(gòu)體,如下,包含了各個匹配字段的類型,對于新字段也需要在這里進(jìn)行添加。
此外,需要調(diào)用函數(shù)key_extract()依次從包頭中提取各個字段放入key中。如果你構(gòu)造了一個數(shù)據(jù)包新協(xié)議字段,就需要在這個函數(shù)中提取相應(yīng)包頭字段賦值給key即可,包頭提取都是對linux的結(jié)構(gòu)體操作,很方便快捷。
2)有了key,那就是內(nèi)核層流表項匹配查找的事情了。由于此次分享圍繞匹配域展開,內(nèi)核中流表匹配查找階段,不涉及具體的匹配字段,也無需做修改添加,因此不具體分析匹配查找流表項的具體過程。
查找結(jié)果無非兩種,查找成功和查找失敗。查找失敗則構(gòu)造upcall上交用戶層繼續(xù)查找處理,但這里需要注意,即使查找到也可能面臨上交處理。因為有一些action無法在內(nèi)核層執(zhí)行,這種action在下發(fā)到內(nèi)核層時已經(jīng)標(biāo)記為OVS_ACTION_ATTR_USERSPACE類型,此時也需要上交用戶層進(jìn)一步匹配處理。
上交用戶層時(主要體現(xiàn)在queue_userspace_packet函數(shù)中),會構(gòu)造上交的數(shù)據(jù)包user_skb(skb_buf結(jié)構(gòu)體),然后通過generic netlink通信機制上交給用戶層。
結(jié)構(gòu)體skb_buf可以簡單理解為這樣的結(jié)構(gòu):Netlink頭部+Attr+Attr+...,Attr是type+len+data結(jié)構(gòu)。Attr主要分為三個類型信息:
?key:即由包頭等構(gòu)造的查詢key,必不可少,數(shù)據(jù)類型type為OVS_PACKET_ATTR_KEY
?userdata:用于匹配成功卻仍要走slow-path的數(shù)據(jù)包,標(biāo)記了action參數(shù)(如原因),數(shù)據(jù)類型type為OVS_PACKET_ATTR_USERDATA
?packet:顧名思義,原始數(shù)據(jù)包。類型type為OVS_PACKET_ATTR_PACKET。
注意,在這里,有兩部分內(nèi)容和匹配域相關(guān),添加新匹配域時候就需要在此處修改源碼:
?A是對于待上交key中含有的各個字段計算總長度(key_attr_size())
?B上傳數(shù)據(jù)user_skb中的key包含很多匹配字段。因此新字段也要從key中提取出來加入到待傳輸?shù)接脩魧拥臄?shù)據(jù)體中(函數(shù)ovs_nla_put_flow()),提取時會用到各個匹配域數(shù)據(jù)的類型(enum ovs_key_attr枚舉類型中定義)。
5、Upcall接收和分類
到這里,已經(jīng)完成和匹配域相關(guān)的多大半內(nèi)容,思路已經(jīng)比較清晰,后面將加快進(jìn)度。
上面說到,內(nèi)核層會封裝含有key、packet和action參數(shù)等內(nèi)容的upcall消息上交用戶層。那么用戶層接收到upcall之后直接匹配表項即可,為什么還要分類呢?(其主要體現(xiàn)在函數(shù)read_upcalls()(ofproto-dpif-upcalls.c))。
先給一張圖:
可以看到,用戶層的upcall結(jié)構(gòu)體有dupcall和miss兩個成員,這就和ovs性能提升密切相關(guān)了。OVS將具有相同key的upcall歸為一類,管理映射到同一個miss中。這樣就完成了相似packet的分類工作,便于后期統(tǒng)一匹配處理,提高效率。
在上面這個過程中,需要從key提取出flow進(jìn)行哈希查找和分類。Flow就是前面講解到的用戶層用于表示匹配域的結(jié)構(gòu)體,OVS調(diào)用函數(shù)flow_extract()函數(shù)從packet與md(metadata元數(shù)據(jù))中解析并構(gòu)造flow賦值給miss->flow,在這里別忘了添加相應(yīng)解析函數(shù)。
其實,分類還包括了對slow path原因的分類處理,因和匹配域無關(guān),就不詳述了
6、用戶層查找匹配處理
完成upcall前期接收和分類工作,下來就是匹配處理了(主要體現(xiàn)在函數(shù)handle_upcalls()(ofproto-dpif-upcall.c))。
這里只有一處和新匹配域添加相關(guān)(odp_flow_key_from_flow__()函數(shù)),因此主要強調(diào)其工作原理。OVS會先分批(之前提到的,劃分為同一個miss的數(shù)據(jù)包)完成用戶層流表匹配查找,然后得到流表項action,并將用戶層action翻譯為內(nèi)核層odp_action,并對屬于slow_path的action數(shù)據(jù)包做特殊標(biāo)記處理(miss.xout->slow),尤其對部分slow_path中slow_action的做help標(biāo)記。之后就可以下發(fā)查找到的表項到內(nèi)核層了,并將數(shù)據(jù)包發(fā)到內(nèi)核層去執(zhí)行流表項的action。
這個過程很合情合理,但標(biāo)記做什么用呢?因為數(shù)據(jù)包匹配到的流表項,其action執(zhí)行只能通過慢通道處理(最典型的就是Controller action,甚至是因為action過多或是數(shù)據(jù)量太大),因此標(biāo)記后,就會將這些含有slow_path action的表項和packet 直接在用戶層完成特殊處理,這基本和內(nèi)核層關(guān)系就不大了,效率自然也不會高。
7、表項和Packet的下發(fā)操作
接下來的工作,就是將表項下發(fā)到內(nèi)核層,并將packet通過netlink機制下發(fā)到內(nèi)核層去執(zhí)行action(主要體現(xiàn)在函數(shù)dpif_operate()中)。
由于之前提到的slow-path原因,OVS會采用兩種形式下發(fā),一種是和slow-path無關(guān)的統(tǒng)一處理下發(fā),一種是和slow-path相關(guān)的單獨特殊處理。
1)統(tǒng)一下發(fā)處理較為簡單,就是批量以廣播形式通過netlink機制下發(fā)到內(nèi)核層,完成流表項在內(nèi)核層的安裝和packet在內(nèi)核層action的執(zhí)行。這里需要注意的是,如果自定義的新匹配域?qū)儆趍etadata類型,如inport這種,那么需要在odp_key_from_pkt_metadata()函數(shù)中,實現(xiàn)將元數(shù)據(jù)內(nèi)容的取出放入request緩存后等待下發(fā)的功能。
2)特殊處理:對于一個需要slow-path處理的packet,其所有動作actions本應(yīng)在用戶層執(zhí)行(即在odp_execute_actions__()函數(shù)),但是執(zhí)行到OVS_ACTION_ATTR_OUTPUT類型action時,不言而喻其最后需要發(fā)送到內(nèi)核層完成轉(zhuǎn)發(fā)。那么這種含有slow_path的流表項是否需要下發(fā)到內(nèi)核層?還記得之前的action翻譯嗎,這種表項會將action翻譯為OVS_ACTION_ATTR_USERSPACE下發(fā)到內(nèi)核層中。如下,用戶層表項到內(nèi)核層表項:
請注意,特殊處理中如果牽扯到set_field action,就需要在odp_execute_set_actio()添加新匹配域的set函數(shù)。
8、內(nèi)核層flow插入和packet執(zhí)行
轉(zhuǎn)了一圈,又回到了內(nèi)核層。在內(nèi)核層完成flow的插入和packet action執(zhí)行工作基本就大功告成了。這里面的原理比較簡單,因此只提及在表項插入過程中與匹配域相關(guān)的地方。
OVS主要在用戶層下發(fā)的表項數(shù)據(jù)中,對含有的匹配字段值進(jìn)行解析和字段有效性檢驗,完成表項插入。匹配字段解析中包含字段長度解析(ovs_key_lens()函數(shù))和字段掩碼解析(ovs_key_from_nlattrs()函數(shù)),有效性檢驗(match_validate()函數(shù))主要完成了匹配字段是否全初始化檢驗、掩碼和值的一致性檢驗等,對于新匹配域,以上幾個函數(shù)需要修改。
9、其他
圍繞著OVS匹配域有關(guān)的處理流程,終于分析完了從表項解析、插入、匹配,執(zhí)行等一系列過程。當(dāng)然,新的匹配域可能還不能很好的運作,因為還差打印顯示和手動插入等功能。這部分比較獨立,簡單提及函數(shù)即可。
打印密切相關(guān):
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函數(shù)()(lib\\odp-util.c)
序號數(shù)FLOW_WC_SEQ
二、安裝檢驗和調(diào)試
對于添加一個自定義匹配域,源碼修改就算完成了,雖然比較繁瑣,但是每一處改動不大,基本照貓畫虎即可。安裝過程簡單,采用常規(guī)安裝OVS的方法即可。
如果安裝后,采用ovs-ofctl命令可以正常添加一條帶有自定義匹配域的流表項,并且數(shù)據(jù)包可以成功如愿以償?shù)钠ヅ涞竭@條表項,那基本就大功告成了。
如果安裝失敗或是匹配不能按照預(yù)想的效果,需要進(jìn)行調(diào)試。調(diào)試一般采用兩種方法,查看log信息和gdb工具調(diào)試:
1)log信息:匹配域的添加涉及用戶層和內(nèi)核層,ovs在用戶層提供了相應(yīng)log函數(shù)VLOG_WARN、VLOG_INFO、VLOG_DBG等,直接使用即可,用戶層log信息一般位于/usr/local/var/log/openvswitch/ovs-vswitchd.log中查看;
內(nèi)核層可以使用printk等函數(shù)添加log,并在/var/log/kern.log中查看即可。
2)采用dbg方式,比較準(zhǔn)確高級的。
三、演示結(jié)果
說了這么多,沒有實驗結(jié)果都是不可靠的。因此,我下發(fā)了一條流表項,包含了一個新的匹配域,并且成功匹配到了數(shù)據(jù)包,達(dá)到預(yù)期效果。可以用ofctl查看用戶層表項,用dpctl查看內(nèi)核層表項。
為了能夠正確添加自定義匹配域,上文對于ovs匹配字段的執(zhí)行流程和基本原理做了分析說明
審核編輯:郭婷
評論