---
# System prepended metadata

title: P4 lab

---

# 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)

