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所示運算。

運算符

描述

and

二目運算符,操作數必須都為布爾型,運算結果為布爾型。

or

二目運算符,操作數必須都為布爾型,運算結果為布爾型。

not

單目運算符,操作數必須為布爾型,運算結果為布爾型。

==,!=

測試是否相等或不等,運算結果為布爾型。

表1 布爾型支持的運算

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

運算符

描述

==,!=

測試是否相等或不等,運算結果為布爾型。

<,>,<=,>=

無符號數比較,操作數的長度(W)要求相同,運算結果為布爾型。

&,|,^

按位運算符,操作數的長度(W)要求相同,運算結果為無符號整型。

運算結果為操作數的補碼。

<<,>>

左移運算符操作數為無符號整型,右移運算符操作數必須是無符號數或非負整數。此運算符為邏輯位移。

+(單目)

單目加運算,效果同no-op。

-(單目)

單目減運算,計算結果為2W減去操作數,W為操作數長度。

+(雙目)

二目加運算,操作數的長度(W)要求相同。計算結果為操作數的算術和,且運算結果長度也必須為W,超過則截斷。

-(雙目)

二目減運算,操作數的長度(W)要求相同。計算結果為操作數的算術差。

*

無符號乘法運算,操作數的長度(W)要求相同,計算結果為無符號數且長度與操作數相等。

表2 無符號整型支持的運算

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

運算符

描述

==,!=

測試是否相等或不等,運算結果為布爾型。

<,>,<=,>=

有符號數比較,操作數的長度(W)要求相同,運算結果為布爾型。

&,|,^

按位運算符,操作數的長度(W)要求相同,運算結果為無符號整型。

運算結果為操作數的補碼。

<<,>>

左移運算符操作數為有符號整型,右移運算符操作數必須是無符號數或非負整數。此運算符為邏輯位移。

+(單目)

單目加運算,效果同no-op。

-(單目)

單目減運算,運算結果有符號整型,且長度與操作數相等。

+(雙目)

二目加運算,操作數數據類型必須相同,運算結果也為同類型。

-(雙目)

二目減運算,操作數數據類型必須相同,運算結果也為同類型。

*

有符號乘法運算,操作數的長度(W)要求相同,計算結果為有符號數且長度與操作數相等。

表3 有符號整型支持的運算

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

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

運算符

描述

==,!=

測試是否相等或不等,操作數必須都是整型(int)運算結果為布爾型。

<,>,<=,>=

有符號數比較,操作數類型都必須是整形,運算結果為布爾型。

<<,>>

右移運算符操作數必須為正整數;左移運算結果和操作數相同。a<<b等價於ax2b,a>>b等價於a/2b。

+(單目)

單目加運算,效果同no-op。

-(單目)

單目減運算,運算結果為整型,且該運算不會導致溢出。

+(雙目)

二目加運算,操作數類型都必須是整型,運算結果為整型,且該運算不會導致溢出。

-(雙目)

二目減運算,操作數類型都必須是整型,計算結果為整型,且該運算不會導致溢出。

*

無符號乘法運算,操作數必須都是整形,計算結果為整形,該運算不會導致溢出。

/,%

二目有符號除法和取模運算,操作數必須是正整數,運算結果為正整數。

表4無限精度整型支持的運算

2.2 數據類型轉換

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

From

To

描述

bit<1>

bool

0代表fasle,1代表true。

bool

bit<1>

0代表fasle,1代表true。

bit<W>

int<W>

保留所有比特位不變。

int<W>

bit<W>

保留所有比特位不變。

bit<W>

bit<W1>

當W>W1時,保留低位W1位長度的數據,當W<W1時新增位補0.

int<W>

int<W1>

當W>W1時,保留低位W1長度的數字,當W<W1時新增位補符號位.

int

bit<W>

將整型轉化為位串,保留地位W位長度數據,溢出需要發出警告並轉化為負數。

int

int<W>

將整型轉化為位串,保留地位W位長度數據,溢出需要發出警告。

表5 合法數據類型轉換

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

bit<8> x;
bit<6> y;
bit<8> z;

表達式

實際實現

x+1

x+(bit<8>)1

z<0

z<(int<8>)0

x<<13

0;//溢出時發出警告

x|0xFFF

x|(bit<8>)0xFF;//溢出警告

表6 隱式類型轉換

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種標准固有元數據及其作用。

字段

描述

ingress_port

數據包的入端口,解析之前設置。只讀。

packet_length

數據包的字節數,當交換機在快速轉發模式下,該元數據不能在動作(action)中匹配或引用。只讀。

egress_spec

在入端口流水線的匹配-動作過程之后設置,指定數據包出端口,可以是物理端口、邏輯端口或者多播組。

egress_port

指定數據包的物理出端口,區別於egress_spec,只能應用於物理端口。只讀。

egress_instance

用於區分復制后數據包實例的標識符。只讀。

instance_type

數據包實例類型:正常(Normal)、入端口復制(ingress clone)、出端口復制(egress clone)、再循環(recirculated)。

parser_status

解析器解析結果,0表示無錯誤,其實數字代表了對應的錯誤類型。

parser_error_loaction

指向P4程序錯誤發生處。

表7 固有元數據

在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中定義的基本動作。

動作

描述

no_op

占位符動作,不做任何操作。

drop

在入口流水線中將數據包丟棄。

modify_field

修改解析后表示中的包頭字段值。

modify_field_with_hash_based_index

使用字段列表索引計算一個值並使用該值生成偏移量。

add_header

為數據包的解析后表示添加包頭。

remove_header

為數據包的解析后表示刪除包頭。

copy_header

復制首部實例。

push

將所有首部實例壓入一個數組,並在頂部添加一個新首部。

pop

將實例數組頂部的元素彈出,后續元素向頂部移位。

count

更新計數器。

meter

執行計量操作。

generate_digest

生成一個報文摘要並發送到接收機。

truncate

在出口處截斷數據包。

resubmit

將原始數據包和元數據重新發送到解析器。

Recirculate

在數據包完成出口修改操作后重新發送。

clone_ingress_pkt_to_ingress

復制原始數據包並發送到解析器。

clone_egress_pkt_to_ingress

復制出口數據包並發送到解析器。

clone_ingress_pkt_to_egress

復制原始數據包並發送到緩存區。

clone_egress_pkt_to_egress

復制出口數據包並發送的緩存區。

表8 基本動作

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

圖6 復合動作定義

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

圖7定義動作-匹配表

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

匹配類型

描述

excat

精確匹配。

ternary

三重匹配,動作-匹配表的每個表項都有一個掩碼,將掩碼和字段值進行邏輯與運算,再執行匹配。為了避免導致多條表項匹配成功,每條表項都需要設定一個優先級。

lpm

這是三重匹配的一種特殊情況,當多個表項匹配成功時,選擇掩碼最長的最為最高優先級進行匹配。

index

字段值作為表項索引。

range

表項中確定一個范圍,字段值在此范圍內皆能成功匹配。

valid

僅用於包頭字段匹配,表項值只能為true/false。

表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的人能有個基本的認識,同時起到拋磚引玉的作用。對P4感興趣的同學可以聯系筆者加入到P4微信交流群中與大牛們一起討論。
郵箱:yangshuai@sdnlab.com 微信號:Redmaple_

 


免責聲明!

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



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