成大資訊所
JAN 17, 2018
@NCKU-IMSLab
瞿旭民
@kevinbird61
Introduction: What is P4?
Build the environment for P4
How does it work ?
Run exercises to illustrate P4
Data Structure(Header)
Parser,Control,Table, Match/Action
Apply algorithm with P4
本篇主軸會以 p4lang/tutorial/SIGCOMM2017 這個 repo 來做介紹
並且以 P4_16 版本為主做討論!
P4 : Programming Protocol-Independent Packet Processors
這段會講述如何於實體機器上面安裝 P4 的開發環境!
有詳細的連結置於 dropbox paper 當中,紀錄一些當時安裝的方式以及遇到的問題!
我目前安裝且成功過的系統:
*.p4
p4c
*.json
target
format
*.json
bmv2
switch
feed
Running P4 program ( Process )
runtime_CLI.py
Rules
Read
Insert
就可以開始實驗囉!
SUME
1G-CML
由於使用的是軟體摹擬的網路介面(由 mininet API 來達到網路介面的實作)
透過 mininet 來建立 network nameless namespace,建立虛擬化的獨立網卡介面, routing table, resolver 設定, 防火牆設定等等
利用 bmv2 提供的 soft switch 建立連接的 interface 後即可讓這些 host 來互相連接!
使用上非常方便,整體透過 mininet 建立好環境後,直接開啟 mn CLI 就可以進入摹擬環境當中,並且透過 xterm 開啟每個 host 各自虛擬化出來的網路介面做使用
P4 提供的 Control 能夠動態對 match-action table 做新增、修改等等的動作,而溝通方式則可以透過這幾項工具來達成:
而目前編譯成功使用的是 Thrift,目前以 thrift 使用(P4_14 版本時以 Thrift 為主,後來演進到 P4_16 後主要以 gPRC 為主)
mininet + software switch
tutorial/
| - vm/
| - * / => using vagrantfile (Build from virtual machine)
| - util/
| - * / => contain essential python script (mininet)
| - exercise/
| - * / => contain all the exercises(等等介紹的部份會在這邊!)
透過 p4c 來 compile 檔案:
p4c-bm2-ss --p4v 16 "basic.p4" -o "output.p4.json"
選擇 P4_16 版本做編譯
從 P4 開發者所編寫的 P4 程式轉換成 json 格式
產生為 simple switch 讀取格式(json)
利用 mininet 來搭建網路環境:
透過 mininet API 來建立 host, switch 以及之間的連線;
連線可依據要實驗的環境而做調整 (bandwidth、使用的 CPU 等等)
* Scenario : basic routing
透過 bmv2 提供的 simple_switch 來執行!
* Scenario : source routing
simple_switch -i 1@s1-eth1 -i 2@s1-eth2 -i 3@s1-eth3
--pcap --thrift-port 9090
--nanolog ipc:///tmp/bm-0-log.ipc
--device-id 0 source_routing.p4.json
--log-console
設定 switch 上的網路介面
controller 溝通使用: thrift
(也可使用 P4Runtime)
以 pcap 格式儲存封包
前面 p4c 產生的檔案!
(範例使用的是 ss )
提供 CLI 工具來做 table 的新增/刪除/修改!
而範例來看,置放在資料夾底下的幾個 sX-commands.txt 則透過 p4app.json 來讓 python 讀取後作為 topo 產生依據
* Scenario : basic routing
table name
table_set_default ipv4_lpm drop
table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:00:00:01:01 1
table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:00:02:02:00 2
table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:00:03:03:00 3
action name
command
起始規則:
像是範例中一開始預設的 scenario
以下選用這幾個範例做說明:
並且使用這幾個範例來帶出 P4 的一些特性!
basic forwarding !
由基礎範例,我們先來介紹幾項 P4 基本性質吧!
資料結構:
PSA 主架構:
從這個範例中,我們先來看到結構吧!
P4 中定義有 control、parser、package 等等
我們先從 package 開始吧!
在 Example 1 中先主要介紹 P4 的基礎架構! 並再後續範例中再來針對 P4 的一些功能做闡述!
Package:
為最主要的結構,其內定義了像是 parser、ingress/egress 等等操作,以及像是 checksum() 的呼叫
程式會依據他定義的項目去做運行!而在範例中,V1Switch 的定義於 v1model.p4 這個 include file 裡頭(於 p4c 編譯時所包含再內)
Parser:
為第1個處理外部封包的地方;下方展示在這個範例中 Parser 的定義,可以由圖像化的方式做理解!
extern packet_in {
void extract<T>(out T hdr);
void extract<T>(out T variableSizeHeader,in bit<32> variableFieldSizeInBits);
T lookahead<T>();
void advance(in bit<32> sizeInBits);
bit<32> length();
}
在 parser 之中,我們可以利用現有的 state 來做封包上資料的擷取;
來看看 packet_in 吧!
是為 packet_in 內定義的 method,主要負責把 source flow 當中的 packet 資料來 map 到 hdr 這個 output variable 裏面,長度由 hdr 的 type "T" 來做決定!
parameter
: optAnnotations direction typedef name
;
...
direction
: IN
| OUT
| INOUT
| /* empty */
;
in, out, inout:
這個是 P4 內定義的 direction keyword,主要表示這個資料流的方向,以及可寫 or 唯讀的作用存在 !
讓我們來看看他的 grammar :
其中 optAnnotations 為輔助使用(像是 Java,C# 中 annotation 用途,不改文法情況下增加功能)
而 direction 便是方向、 typedef 則是該 variable 的型態、最後才是變數名稱 name
(* annotation 可參考 spec 第 17 章 ! )
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition accept;
}
state:
* 在 state 中做 extract 到 hdr 動作! 以 state machine 方式運作
(p4c 有提供 graphviz 輸出!)
由 architecture 提供的 object ,讓 P4 程式能夠使用;
像是一般的物件導向語言中定義的 class 一樣,能夠提供封裝來實作一個物件的功能(像是屬性、method 等等)
像是剛剛用的 packet_in,以及實作的 checksum:
extern Checksum16 {
Checksum16(); // constructor
void clear(); // prepare unit for computation
void update<T>(in T data); // add data to checksum
void remove<T>(in T data); // remove data from existing checksum
bit<16> get(); // get the checksum for the data added since last clear
}
注意! extern 只要描述這個物件的行為,並無實作!
(對於 extern 內的 method,是由 P4 內部定義所實作 e.g. 由 target device 提供)
在 P4 中,Metadata 用於讓 P4 在 pipeline 中不同 stage 處理之間可以傳遞訊息與資料
和 packet_in 等等相似,內部也可以存放像是進入的 port、傳輸目的、用於 packet 標記的 timestamp 等等
而這些 metadata 並不涉及改變 packet 解析後的表現!
* 於 ecn.p4 擷取,可看到使用 standard_metadata 做 queue length 標記使用
來看看範例其中一個呼叫 extern object 的使用:
control MyComputeChecksum(
inout headers hdr,
inout metadata meta)
{
apply {
update_checksum(
hdr.ipv4.isValid(),
{ hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.diffserv,
hdr.ipv4.totalLen,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.fragOffset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr },
hdr.ipv4.hdrChecksum,
HashAlgorithm.csum16
);
}
}
basic.p4
extern void update_checksum<T, O>(
in bool condition,
in T data,
inout O checksum,
HashAlgorithm algo
);
v1model.p4
用以檢查 payload 的 checksum
若發現錯誤,則會透過 standard_metadata 做 checksum_error bit 的設定,表示錯誤發生
在 P4 當中,PSA(Portable Switch Architecture) 架構如下:
剛才提到的 Parser
接續於 Parser 後,使用 Parser 解析 Header 資料做處理;並決定 packet 的出口、也決定會放到哪一個 queue 中
可以分成幾個部份做說明: table, key, action
為 table 中 match 的依據!後面接的是比對的 policy( P4 Programmer 無法自行定義 );在 core.p4 中定義的基本款三種: exact、ternary、以及 lpm
鍵值比對後,依據結果來選擇 actions 做執行!
以上述例子來看,比對成功後即執行 ipv4_forward 這個對應的 action
在進入 action 前,先來剖析了解 match-action 機制吧!
Action:
基本上可以理解為 function,分兩種: Directional 及 Directionless;
並且 action 的 parameter 的來源有兩種:只透過 control plane 做指定、或是透過其他 calling action 來 assign (行為像是帶有 "in" direction 的變數)
Action Type | From |
---|---|
Directional | Data plane |
Directionless | Control plane |
match policy:
key 值匹配的決定方式,由 P4 提供,一般 programmer 無法自行定義與新增; 透過 match_kind 的關鍵字做定義
大多由 core.p4、v1model.p4 所提供!
Match type | 說明 |
---|---|
exact | key 值需完全匹配 |
ternary | 透過一組 mask 來做匹配 |
lpm | longest prefix match |
如何執行一個定義好的 table?
以範例中 control block 做解釋
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
... (action goes here)
apply {
if (hdr.ipv4.isValid()) {
ipv4_lpm.apply();
}
}
}
以傳入的 hdr 做 condition 判斷
如果符合規定,就可以呼叫 <table_name>.apply() 來啟動剛剛定義過的 table 機制 (當然 action 還可以再呼叫另一個自定義的 action,後面再詳細介紹)
運行範例:
...
apply {
if (hdr.ipv4.isValid()) {
ipv4_lpm.apply();
}
}
匹配 ipv4_forward
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
drop;
NoAction;
}
size = 1024;
default_action = NoAction();
}
呼叫 table : ipv4_lpm
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
進入 action 內,即可對封包內部做改動!
基本上到這邊,已經差不多完成了!
來到最後一個部份:Deparser
Deparser 會依據 control block 給予的設定,來對 queue 內的 packet 做複製、轉發、丟棄等等動作(可參考圖中的連線)
如何達到發送?
透過 emit() method !
control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet);
packet.emit(hdr.ipv4);
}
}
於 packet_out 型態中定義了 emit 這個 method
透過呼叫 emit 來把 in 的 hdr 資料倒進 packet_out 的 packet 當中,告知其可以準備做下一步
讓我們來看看 emit() 做了什麼!
packet_out {
byte[] data;
unsigned lengthInBits;
void initializeForWriting() {
this.data.clear();
this.lengthInBits = 0;
}
void emit<T>(T data) {
if (isHeader(T))
if(data.valid$) {
this.data.append(data);
this.lengthInBits += data.lengthInBits;
}
else if (isHeaderStack(T))
for (e : data)
emit(e);
else if (isHeaderUnion(T) || isStruct(T))
for (f : data.fields$)
emit(e.f)
// Other cases for T are illegal
}
}
依據給定的 header 來做 append data 的動作!
可以看到當 header 合法時( e.g. header stack, header union 或甚至是由這些型態 recursively 組合成的結構 ),會去抓取其長度後加入到自己目前 packet_out 的 data 的 buffer 內
* 參考 spec Chapter 14
這麼一來
我們便完成一個擁有基本 forwarding 功能的
switch 囉! 剩餘的 send.py , receive.py 由 DEMO 中做介紹
[1] p4-tutorial
[2] P4-Play
source routing !
1
2
3
3
2
1
1
2
3
黃色: switch port 標示
紅色: 標示流向
輸入序列:
2,3,2,2,1
由於在 Example 1 大多已經介紹過主要架構,所以在這部分將主軸放在 "如何操作" 上
在第一個基本 forwarding 中,我們只有簡單的設定,將所有封包都往 h2 送 ( 在 send.py 當中 )
而現在則需要在多個 switch 上面做特定輸入來做 forwarding; 接下來可以透過 P4 來實作這部分的功能 !
理解 P4 如何運作
操作 P4
修改 Data Structure !
額外新增自定義的型態 - srcRoute_t 來作為每個要 hop 的 port;
struct headers {
ethernet_t ethernet;
srcRoute_t[MAX_HOPS] srcRoutes;
ipv4_t ipv4;
}
header srcRoute_t {
bit<1> bos;
bit<15> port;
}
bos: bottom of stack,表示該 element 為 stack 中最後一筆
port: 紀錄目前這個 hop 要走哪一個 switch port 出去
因為 P4 內無法針對這個變數取 element 長度,所以 bos 的欄位作取代,用以標記這個是最後一個,parse 完之後就可以進到下一個 parsing process - ipv4
* 在 P4 中沒有 loop 的功能!只能夠過上述的 iterative 方式來達到相同功能!
透過 p4c-graphs 來生成 diagram (由上而下,從 start 開始)
可以看到多了一個 state:
parse_srcRouting,利用剛才新增的 bos 來判斷是否為最後一個 hop,並且進入 parse_ipv4
這邊邏輯上是去判斷目前進入的 srcRoutes 中正在處理的 element 的 bos 是否為 1;若為 1,則表示該 element 為 stack 中最後一個;便可以進入下一個 state
若不是,則呼叫自己來取出要跳往的下一個位置!並儲存在 要輸出的 hdr 內!
透過提供的 next, last 的方式做寫入!
state parse_srcRouting {
packet.extract(hdr.srcRoutes.next);
transition select(hdr.srcRoutes.last.bos){
1 : parse_ipv4;
default : parse_srcRouting;
}
}
這邊介紹一下在 parse_srcRouting 中使用到的特殊方法 -
由 headers 所提供的 next, last
packet_in
目前讀取位置
size
由 extract 內的 variable 決定 size 大小
copy
(out) hdr
size
目前讀取位置
next:
hdr (headers) 實作的 method,提取下一個 element,以提供寫入的位址;若第一次 call,則提取第一個 element
last:
從目前 hdr 中提取剛才用 next 存入的那個 element 之位置
(out) hdr
last
值得注意的是,由各自 object
來保存讀寫位置( e.g. buffer index )!
next
到這邊的操作為止,我們成功的在 parser 中把資料從 packet 中擷取到我們訂定的 data structure 當中 ( e.g. hdr);
現在我們可以進到 "依據擷取資訊來決定 packet 的行為" 這一步驟,那麼就需要修改到 table 裡頭的內容來達成!
這裡便接收來自 parser 處理過後的 hdr,來做進一步的操作!
這裡定義了 switch 的動作:
當發現 bos == 1 時,該 switch 便會執行 srcRoute_finish(),並且把狀態設成 TYPE_IPV4,讓他能夠在該 switch 時被接收;
若非 1,表示還需要繼續 routing,便會 pop 出目前的 srcRoute 物件,並傳到該物件指定的 port (e.g. 下一個 switch)
最後在 deparser 內呼叫 packet_out 的 emit method,來把這3個 header 指定做 transmit 的設定,使其對應的 payload 能夠在可傳送時 copy 到 deparser 的 output buffer 當中
看看是否於 host 2 能夠接收到 routing 多次後的封包
ECN !
(Explicit Congestion Notification)
一般流量
大流量 (iperf)
Congestion!!
利用 iperf 這項工具,來達到瞬間大量封包的功能!
有了基本操作知識後,我們可以更上一層樓,到達施做演算法的部份!
ECN (Explicit Congestion Notification):
對於擁擠的網路控制的端對端通知,從而避免丟包;
傳統 TCP/IP 網路通過丟棄封包來表明 channel 阻塞;在無ECN時,擁塞指示回傳是通過檢測分組遺失來間接實現
而 ECN 成功協商的狀況下, ECN 感知路由器可以在 IP 頭中設定一個標記來代替丟棄封包! ( 而不是 drop! ) → 以標示 “阻塞即將發生” ,回應給傳送端 !
diffserv ( 6 bits )
ecn ( 2 bits )
tos ( 8 bits ) =
+
ECN 為一個 2 bits 的資料內容來標示目前網路情況:
ECN | 意義 |
---|---|
00 | 表示不支援 ECN 傳輸 |
10 | 支持 ECN |
01 | 支持 ECN |
11 | 發生擁塞情況 |
所以現在我們得運用剛才的學習內容,來做到修改 ECN 項目,判斷網路是否壅塞後,把結果塞進要回去的封包當中!
看看是否在 h2 看到 ecn 的改變!
MRI !
(Multi-Hop Route Inspection)
一般流量
大流量 (iperf)
Congestion!!
和 ECN 相似,不過此時著重在封包修改;我們可以在 h22 看到修改的結果,e.g. 壅塞在 s1 上的 queue 長度
p4c 的專案編譯安裝完成後,提供了三個執行檔方式來針對不同對象產生目標檔,分別是:
參考專案目錄: P4_Play
中文相關論壇,淺顯易懂
規格書 P4_16, PSA 架構
官方最新穩定架構於此
台灣 P4 推廣組織,更新頻率較慢,不過提供 ONOS 方面的支援,以及 P4Runtime (PI library 的使用)
所有 P4 官方相關原始碼集散地
找到 nameless namespace!