最近因為研究的關係開始研究 P4 這一個語言,所以順手將自己看到的東西做一個筆記,後續開發可以在拿出來參考。
簡介
P4語言是由數間大學(Stanford, Princeton)以及企業(Barefoor Netowrk, Intel, Google, Microsoft)於 2014 SIGCOMM 中提出,主要希望解決的問題是一般的 Switch,包含 SDN Switch 都只能夠處理一般的封包,例如 Ethernet、VLAN、IPv4 等等。
透過 P4,開發者能夠直接定義出一個 Switch 能夠處理的封包格式,舉例來說定義一個新的 Ethernet type,或是直接不用 Ethernet header,用自行定義的封包結構,另外就是他可以不被侷限現在特定的硬體之下執行,只需要有對應的編譯器就可以佈署,這部份之後會在說明。
下圖是 P4 的 Abstract Forwarding Model(取自Spec)
一個 Data plane 中有幾個比較重要的地方:
- Parser(本篇介紹)
- Tables (Match + Action)
- Control program
這一系列的東西都是需要透過 P4 語言去定義的
透過 P4 定義完成之後,用編譯器編譯成硬體的目標語言(例如 ARM binary、FPGA 語言等等),最後把他們佈署到硬體(或軟體Switch)上,成為上圖的 Switch Configuration。
以下一一簡介各部分區塊的定義方式:
Parser:
Parser 包含了兩個部分:Header 以及 Parser。
Header 代表了有哪些格式的 Header 可以使用,例如 Ethernet header。
在 p4 裡面,Header 的定義方式如下(以 Ethernet 為例):
header_type ethernet_t {
fields {
dst_addr : 48;
src_addr : 48;
ether_type : 16;
}
}
上述是 Header 的定義方式,我們可以建立一個 header.p4 的檔案,然後在透過 include 的方式將他引用到主要的 p4 中。
順帶一提,裡面的數字全部都是以 bit 為單位的,Header 跟 C 語言中的 Struct 有點相似,他是需要另外在定義成為真正 Header 的實體(header instance),如下:
header ethernet_t ethernet;
定義好 Header 之後,再來就是定義 Parser 了,也是一樣的,可以將 parser 定義在一個獨立的 parser.p4 中,以方便程式重構。
在 P4 當中,永遠都會有一個起始的 Parser,我們稱作 start:
parser start {
return next_parser;
}
在每一個 parser 中都會依據目前所分析的內容來決定下一個 parser,直到回傳的內容為 “ingress”(或其他 control function) 為止,下面用 ipv4 為例子:
parser start {
return parse_ethernet;
}
parser parse_ethernet {
extract(ethernet);
return select(latest.ether_type) {
0x0800 : parse_ipv4;
default : ingress;
}
}
parser parse_ipv4 {
extract(ipv4);
return ingress;
}
這邊有幾個需要補充的東西:
- extract : 將目前的 Packet 以特定的 header 取出來,取出來的資料長度以 header 定義的為主
- return : 透過 return 的方式決定要前往哪個 parser、control function(後面會補充),可以直接 return 或是使用 select。
- select(select_exp) : 蠻像 C 語言中的 switch case,依據特定的 field 數值去決定要哪一個 parser 或是 control function。
- select_exp : 依據 spec,他可以是:
- field_ref : 如 ethernet.ether_type
- latest.field_name : 以最後 extract 的 header 為主,取用他的 field
- current (offset, length) : 以目前的 packet offset 位置開始某固定長度所取得的數值。
另外一個就是 parser 可以 return 一個 exception 出來,格式如下:
parser_error parser_exception_name;
parser_exception_name 有很多種,像是 p4_pe_out_of_packet 等等,這篇就不佳闡述了。
而如果我們要處理一個 exception 的話,則需要先定義好 exception handler。
parser_exception p4_pe_out_of_packet {
/* statements */
/* return or parser_drop */
}
處理一個 exception 的方式有很多,最終的結果只會有兩個,一個是跳至指定的 control function,或是直接將該 packet drop 掉。
在 egress 階段開始時會先對 packet 做 deparsing 的動作,亦即將 ingress 所做出的修改(add header、modiffy header等)重新封裝回 packet 當中。