P4語言編程詳解


在文章《P4:開創數據平面可編程時代》中介紹了P4的架構特性、交換機結構以及P4程序的工作原理,本篇為大家介紹P4語言編碼及規范,從編碼角度去理解P4程序的工作流程。

1.源碼目錄結構

P4項目源碼可以在github上直接獲取(https://github.com/p4lang)。P4項目由很多個單獨的模塊組成,每個模塊就是一個子項目,下面分別簡單介紹一下各模塊的功能。

(1)behavioral-model

模擬P4數據平面的用戶態軟件交換機,使用C++語言編寫,簡稱bmv2。P4程序首先經過p4c-bm模塊編譯成JSON格式的配置文件,然后將配置文件載入到bmv2,轉化成能實現交換機功能的數據結構。

behavioral-model模塊是架構無關的,可以實現各種P4編程目標。該模塊主要實現三個目標,其中最重要的是simple_switch,即實現P4語言標准中抽象交換機模型。另外兩個目標是(simple_router,l2_switch),這兩個目標是作為教學示例。

(2)p4-hlir

將P4代碼轉換成高級中間表示的前端編譯器,目前的高級中間表示的展示形式與python對象的層次結構相同。該編譯器的目的是使得后端編譯器開發者從語法分析和目標無關的語義檢查的負擔中解放出來。

(3)p4c-bm

behavioral modal的后端編譯器,建立在p4-hilr的頂部,該模塊以P4程序作為輸入,輸出一個可以載入到behavioral model的JSON配置文件。

(4)p4-build

需要手動生成的基礎設施庫,為執行P4程序編譯、安裝PD庫。

(5)switch

內含switch.p4程序樣例以及通過SAI、SwitchAPI和Switchlink操作交換機所需的所有庫,可獨立於p4factory運行 。

(6)ntf(Network Test Framework)

網絡測試框架,內含用以執行bmv2上應用的網絡測試樣例。該框架中集成了mininet和docker,方便用戶進行測試。

(7)p4factory

內含整套用以運行和開發基於behavioral model的P4程序環境的代碼,幫助用戶快速開發P4程序。

(8)ptf

數據平面測試框架,基於unittest框架實現,內含標准Python版本。該框架中的大部分代碼從floodlight項目中的OFTest框架移植而來,框架的實現和開發可參考OFTest框架文檔。

(9)scapy-vxlan

基於Scapy項目,barefoot對其進行了定制,支持更多協議的數據包包頭的偽造和解析,目前支持 VXLAN和ERSPAN-like(Scapy本身並不支持)。

(10)tutorials

P4語言教程,內含8個教程,覆蓋了P4語言中的解析器、動作、狀態存儲、匹配-動作表、等基礎組件。

1)cpoy_to_cpu:基本動作clone_ingress_to_egress教程

2)meter:計量表教程

3)TLV_parsing:IPv4數據包解析教程

4)register:寄存器讀寫狀態教程

5)counter:計數器教程

6)action_profile:ECMP動作摘要教程

7)resubmit:數據包沖提交到入端口流水線教程

8)simple_nat:TCP流量的完全圓錐形NAT網絡教程

注:P4語言項目庫中的SAI、mininet及thrift是從其他開源項目完全fork而來,這里不展開討論。

2.P4語言標准

當前P4語言標准的最新版本為《The P4 Language Specification Version1.1》(以下簡稱V1.1),目前版本的P4語言編譯器已經基本實現了P4語言標准中的絕大部分特性 ,部分特性尚在開發之中。

2.1 基礎數據類型及操作

P4語言中定義了5種基礎數據類型,分別是:bool、bit、int、varbit、int。(注:此處W代表長度,通常使用十進制數字表示,如bit)通常情況下,不同的數據類型之間可以相互轉換,並且所有的二目運算符都要求數據類型保持一致,除了位移操作符(shifts)。

(1)布爾型(bool)

布爾型(Boolean),值為true或false,非整數型。布爾類型數據可進行如表1所示運算。

(2)無符號整型(bit)

無符號整型(unsigned integers)也叫位串(bit-string)。位串是以比特位形式表示的任意長度的數(如:bit<127>,表示長度為127比特的位串),但如果需要對位串進行某些數學運算時,位串長度必須是8的整數倍(如:16、32、64bit)。無符號整型支持如表2所示運算。

(3)有符號整型(int(W))

有符號整型(signed integers)支持如表3所示運算。

(4)變長位串(varbit)

變長位串(dynamically-sized bit-strings)不支持算術、比較、按位運算,甚至不支持類型轉換。該數據類型在定義時會指定一個靜態的最大寬度值,解析器會提取變長位串數據並設置一個值作為長度。

(5)無限精度整型(int)

無限精度整數(infinite-precision integers)支持如表4所示運算。

2.2 數據類型轉換

再P4預研中,對數據進行運算時大多時候都要保證操作數數據類型的一致性,P4也提供了基礎的數據類型轉換功能,表5中列出了所有合法的數據類型轉換。

在P4程序中對數據進行運算時,除了用戶在編寫程序是手動轉換數據類型,P4編譯器在某些情況下也會自動將數據進行類型轉換,這種轉換是強制的、自動的的隱式類型轉換。如表6所示,例舉了P4程序中常見的幾種隱式類型轉換的情況。

2.3 基礎語言組件

P4程序中有5個語言組件:首部(Headers)、解析器(parsers)、表(Tables)、動作(Action)、流控制程序。

(1)首部

首部類型是由成員字段組成的有序列表,每個字段都有其名稱和長度,每一種首部類型都有對應的首部實例來存儲具體的數據。首部分為兩種,一種是包頭(Packet Headers),另一種是元數據(Metadata)。

包頭用以描述數據包結構,以IPv4協議為例,圖1為 IPv4報文頭部結構,IPv4報頭有20字節固定長度部分和可選字段、填充字段的可變部分,每個字段的作用這里不再贅述。

圖1 IPv4協議報頭結構 使用P4語言定義IPv4的包頭類型示例如圖2所示:

圖2 IPv4 包頭定義

對照圖1中IPv4報頭結構可以比較容易理解上述P4語言代碼——按照IPv4報頭格式,定義了一個包頭並實例化。

這里需要區分“包頭”,“報頭”的關系。如果沒有特殊指出,本文中的“包頭(Packet Header)”指的是P4語言中的術語,而“報頭”指的是數據包的報文頭部。

元數據用來攜帶數據和配置信息,元數據的申明與包頭類似,但在實例化時不同,而且包頭和元數據在字段值的約束上存在一定的差別。元數據分為兩種,一種是用來攜帶P4程序運行過程中產生的數據的用戶自定義元數據(User-Defined Metadata),如首部字段的運算結果等。另一種是固有元數據(Intrinsic Metadata),用於攜帶交換機自身的配置信息,如數據包進入交換機時的端口號等。

圖3 元數據定義 用戶可以使用自定義的元數據來攜帶任意數據,但固有元數據在編譯器中具有特定的意義。V1.1中定義了8種固有元數據,這些元數據攜帶了數據包相關的狀態信息,表7中展示8種標准固有元數據及其作用。

在P4語言中定義首部類型有以下幾點需要注意:

1)包頭類型的長度需要字節對齊,即長度必須是8bit的整數倍。

2)包頭中字段長度可以是可變值(該特性在P4語言規范中規定,但當前編譯器版本並為實現,后續版本會支持)也可以是首部中其他字段值計算后的值。而元數據中的字段長度只能是定值。

3)只有包頭能夠實例化成數組,元數據則不行。

4)實例化時,首部中已定義名稱的字段的值會被初始化成程序中的指定值,如果首部中只定義字段名稱而未指定值,字段的值將會被初始化成0。

(3)解析器

一個P4程序中往往定義了大量的首部和首部實例,但並不是所有的首部實例都會對數據包進行操作。解析器工作時會生成描述數據包進行哪些匹配+動作操作的中間表示( Intermediate Representation),在P4中稱之為解析后表示(Parsed Representation),這些解析后表示規定了對數據包生效的實例,是一組對數據包生效的實例的集合。

P4語言中解析器采用有限狀態機的設計思路,每個解析器方法視為一種狀態。當解析器工作時,會將當前處理的數據包頭字節的偏移量記錄在首部實例中,並在狀態遷移(調用另一個解析器)時指向包頭中下一個待處理的有效字節。以以太網幀的解析器為例,用數據包類型代對應解析器,將每個解析器作為一種狀態,用箭頭表示狀態遷移,則可以構建出如圖2 所示的以太網幀的解析器的狀態遷移圖。

圖4 以太網幀解析器狀態遷移圖 圖5展示了以太網幀和IPv4數據包解析器定義示例。

圖5 解析器定義

一個解析方法/狀態可以以下四種方式結束:

1)return 一個流控制程序名

2)return一個解析器名

3)發生顯式錯誤

4)發生隱式錯誤

P4語言中流控制程序和解析器的命名空間是共用的,所以在定義解析器和流控制程序的時候需要注意不能重名,否則會導致P4程序錯誤。

(3)動作

P4語言中的動作主要分為兩種,基本動作(Primitive Actions)和復合動作(Compound Actions)。基本動作包括:數據包處理運算符(如添加、刪除或修改包頭)、基本的算術運算符、哈希運算符和統計跟蹤運算符(如計量、測量)。復合動作由基本動作組合而成,由用戶自行定義。表8中展示了P4中定義的基本動作。

這些動作高度抽象且與協議無關,以實現P4語言處理數據的協議無關性。同時,復雜的操作及流程可以通過組合不同基本操作(即復合操作)完成,從而保障了P4語言對各種協議的支持以及擴展性。圖6展示了P4中復合動作定義的示例。

圖6 復合動作定義

(4)匹配-動作表

P4語言中的匹配-動作表定義了匹配字段、動作及一些相關屬性(如表容量),當匹配-動作表中定義的字段與數據包匹配成功時,則執行對應的動作;若匹配不成功則標記為“失配(miss)”,並執行默認操作。匹配動作表的定義如圖7所示。

圖7定義動作-匹配表 P4語言的匹配-動作表支持多種匹配類型,如精確匹配、最長前綴匹配、范圍匹配等。如表9所示,展示了動作-匹配表支持的匹配類型。

(4)流控制程序

P4語言中匹配-動作表中規定需要匹配的字段和需要執行的操作,流控制程序則用來規定匹配-動作表的執行順序。

以P4語言定義二層轉發流程為例,數據包首先進行L2轉發表(l2_fwd)匹配,然后根據數據包的以太網目的地址是否匹配路由器自身的MAC地址(通過查找所屬的router_mac表)決定是否經過l3路由表(ipv4_fib_lpm和upv6_fib_lpm),再根據IP包頭類型(IPv4或IPv6),數據包匹配不同的L3路由表,最后通過訪問控制列表來控制數據包是否通過。

圖8 流控制程序定義

2.4 狀態存儲

包頭和元數據實例中的數據只能存在對某個數據包解析的過程中,解析下一個數據包時,這些實例會重新初始化。而計數器、計量器和寄存器中的數據在整個流水線中長期存在,所以稱之為狀態存儲。

(1) 計數器

計數器附加在每個表項之后,並在完成一次匹配並執行對應操作后自增1。計數器中定義了7種屬性,下圖展示了V1.1中計數器的定義方式。

圖9 計數器定義 1)Name

計數器名稱,指向該計數器,P4編譯器中通過名稱+索引的方式確定一個計數器實例。

2)min_width

編譯P4程序時,編譯器分配給計數器的大小並不是完全固定的,該屬性指定了分配給計數器的最小長度。

3)saturating

如果計數器中設定了該屬性,則當計數器到達上限時停止計數,否則計數器將清零並重新開始計數。

4)direct

如果計數器中設定了該屬性,則計數器綁定的匹配-動作表中無需指定count動作來更新計數器,計數器會自動更新。若在匹配動作表調用count動作更新計數器,則編譯器報錯。

5)static

如果計數器中設定了該屬性,則必須在匹配-動作表中調用count動作更新計數器。

6)instance_count

該屬性用以記錄計數器實例數,如果計數器設定了direct屬性,則無法在計數器中設定該屬性;如果計數器中未設定direct屬性,則該屬性必須設定。

7)type

V1.1中的計數器類型有3種: bytes、packets、bytes_and_packets。

(2) 計量器

計量器的定義與計數器類似,計量器中定義了6種屬性,下圖展示了V1.1中計數器的定義方式。

圖10 計量器定義 1)name

計量器名稱,指向該計量器。

2)direct

如果計量器中設定了該屬性,則計量器綁定的匹配-動作表中無需指定execute_meter動作來更新計量器,計數器會自動更新。若在匹配動作表調用execute_meter動作更新計量器,則編譯器報錯。

3)static

如果計數器中設定了該屬性,則必須在匹配-動作表中調用execute_meter動作更新計數器。

4)instance_count

該屬性用以記錄計量器實例數,如果計量器設定了direct屬性,則無法在計量器中設定該屬性;如果計量器中未設定direct屬性,則該屬性必須設定。

5)type

V1.1中的計量器類型有2種: bytes、packets。

6)result

V1.1中的計量器的輸出結果有3種,分別用三種顏色標記:紅色(P4_METER_COLOR_RED)、黃色(P4_METER_COLOR_YELLO)和綠色(P4_METER_COLOR_GREEN),輸出結果存在一個2bit長度的字段中。

(3) 寄存器

寄存器定義了5種屬性,下圖展示了V1.1中寄存器的定義方式。

圖11 寄存器的定義 1)name

寄存器名稱,指向該寄存器,P4編譯器中通過名稱+索引的方式確定一個計量器實例。

2)width_or_layout

width和layout屬性二選一,width為指定一個確定的長度,而 layout是直接通過名稱引用已定義的包頭結構。

3)direct_or_static

與計數器和計量器中的定義類似,雖然寄存器不能直接在匹配過程中使用,但是作為modify_field動作的數據源,將當前寄存器中的數據復制到數據包的元數據中,並在后續的匹配中使用。

4)instance_count

該屬性用以記錄寄存器實例數,如果寄存器設定了direct屬性,則無法在寄存器中設定該屬性;如果寄存器中未設定direct屬性,則該屬性必須設定。

3 結語

以上是參考P4語言規范標准並結合個人的理解所寫,希望能讓不了解P4的人能有個基本的認識,同時起到拋磚引玉的作用。


免責聲明!

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



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