# P4 lab ## Part 1 將目前使用者加到docker群組裡面,當docker service起來時,會以這個群組的成員來初始化相關服務 ``` sudo chmod 777 /var/run/docker.sock ``` 進入獨立的venv python環境 ``` python -m venv venv && . ./venv/bin/activate ``` 查詢ASCII ``` python -c "print(hex(ord('+')))" ``` ### Get P4 code Get the basic p4 codebase from ONOS. ![](https://i.imgur.com/pWEoWZS.jpg) ### Compile first P4 code ``` $ p4c-bm2-ss --arch v1model \ -o build/bmv2.json --p4runtime-files build/p4info.txt \ src/basic.p4 ``` 完成後會在build資料夾中生成以下檔案 ![](https://i.imgur.com/SmgPemb.jpg) 接著建立一python檔(topo.py),執行並建立基本網路拓樸。 ![](https://i.imgur.com/FJr8Brj.jpg) ### Network Topology Set up switches with p4runtime-shell, and then add flow rules to the switch through p4 program. 上圖可以看到所建立的拓樸有兩台switch,所以我們需要用p4 language去控制每一台switch。 ``` $ python -m p4runtime_sh --election-id 0,1 \ --grpc-addr localhost:50001 \ --config "build/p4info.txt,build/bmv2.json" ``` ![](https://i.imgur.com/DigQGoR.png) 接著輸入以下指令為讓封包進入這台switch由port 1進、port 2出。 ``` te = table_entry["ingress.table0_control.table0"]( action="ingress.table0_control.set_egress_port" ) te.priority = 1 te.match["standard_metadata.ingress_port"] = "2" te.action["port"] = "1" te.insert() te = table_entry["ingress.table0_control.table0"]( action="ingress.table0_control.set_egress_port" ) te.priority = 1 te.match["standard_metadata.ingress_port"] = "1" te.action["port"] = "2" te.insert() ``` ![](https://i.imgur.com/qTESNIN.jpg) 兩台switch設定完成後,回到mininet即可以讓h1與h2互通。 ![](https://i.imgur.com/xLxqJTl.jpg) ## Part 2 ### P4 architecture ![](https://i.imgur.com/EaxdpwJ.jpg) **Parser $\rightarrow$ Control Pipeline $\rightarrow$ Control Deparser** ### Parser & Deparser 開啟mininet ![](https://i.imgur.com/YerYqQn.jpg) 發送UDP封包 ``` $ docker exec -it p4mn_01 m h2 bash $ nc -u 10.0.1.1 3000 1 + 1 ``` 開啟wireshark (若遇到wireshark權限問題,待補) ![](https://i.imgur.com/opHYXo6.jpg) ![](https://i.imgur.com/o9iNkph.jpg) **實驗目標:** Parser:如果UDP port是3000,解析內容(兩個數字+一個運算符號)。 透過**header stack**來儲存內容。 **步驟** 編輯parsers.p4、headers.p4、custom_headers.p4 在parse_udp底下可以透過transition select來轉換到不同的state,在此預設的情況是不會往下送,當滿足以下條件則進行parse_math_udp $\cdot$ UDP data length > 0 $\cdot$ UDP port = 3000 ### Step 1 - 1: ![](https://i.imgur.com/H0SE7IC.jpg) 從維基百科上面的資料可以得知udp協定中的header為8個bytes,從剛剛wireshark的封包可以看到他的length為14也就是整體的長度,因此可以得知data length為6 bytes。所以我們要定義一個UDP data length,且他必須大於0。在header.p4中可以看到他定義的UDP整體長度為length_,所以我們在定義UDP data length為: **headers** ![](https://i.imgur.com/1bul7eE.jpg) 這個length_是指udp封包的整個length。 **custom headers** ![](https://i.imgur.com/Ig8ABnt.jpg) local_metadata主要是定義、儲存在parser.p4得出的結果 **parser** ![](https://i.imgur.com/L0Fqnln.jpg) ### Step 1 - 2: 接著需要寫buffer來儲存num1、space1、num2、operator、space2。要注意的是,它的結構是由struct包著header,再由header包著field才比較不會有bug。 **headers** ![](https://i.imgur.com/TQTSoEO.jpg) **custom headers** ![](https://i.imgur.com/FTdw5wV.jpg) ![](https://i.imgur.com/2HuxZBs.jpg) (打錯字,已更正) **parser** ![](https://i.imgur.com/iPxhdFR.jpg) 建立header stack來暫存數字及運算符號。需要注意的是定義的udp_buffer裡面的元素是char,-CHAR_0是為了將字元轉為數字。 ![](https://i.imgur.com/UcRuBPN.jpg) **define** ![](https://i.imgur.com/7htG15W.jpg) 定義字元0的ASCII為0X30。 ### Control Pipeline **實驗目標:** Table entry具有三個欄位:Key、Action、Action Data ![](https://i.imgur.com/zWCtKAU.jpg =70%x) 將前面的數字及運算符號進行運算,並把結果資料寫回UDP封包。 **步驟** ### Step 2 - 1: basic.p4 ![](https://i.imgur.com/7elHFZS.jpg) ![](https://i.imgur.com/EsMjAdX.jpg) ![](https://i.imgur.com/qJcpZcZ.jpg) define.p4 ![](https://i.imgur.com/4DZbBNI.jpg)