# 虛擬化-lab 2
## lab2 目標
撰寫改變網路行為的 eBPF 程式,了解 `bpftool` 的操作方式和 eBPF 物件的生命週期
1. 開發 XDP 程式: `ping` 使用 ICMP 協定,過濾 ICMP 網路封包,在進入核心網路協定層(network stack)前將封包丟棄
2. 開發 sockops 程式:透過 eBPF 在 socket 層進行轉傳,加速封包傳送
## 目標一:XDP
### XDP 介紹
XDP 附著點在網卡的接收方向,會在進入核心網路協定層前進行處理,可以對封包進行檢查、修改、轉傳或丟棄。
#### XDP 回傳值
- **XDP_PASS**:以正常方式處理資料包。
- **XDP_DROP**:丟棄資料包。
- **XDP_TX**:將封包傳送回其到達的介面。
- **XDP_REDIRECT**:將封包轉送到另一個介面。
- **XDP_ABORTED**:丟棄資料包並發出警告。
### ping 封包和交握說明
- 封包說明:以 ICMP 的封包來說, XDP 得到的封包從最外層開始,包含 ETH, IP 和 ICMP 。

- 交握說明:
<img src="https://hackmd.io/_uploads/BJIVyxq-yx.png" height=450 style="display: block; margin: auto;">
### 封包處理流程
`vmlinux.h` 定義各協定標頭的結構體 `struct ethhdr`, `struct iphdr`, `struct icmphdr`。協定的類型 `ETH_P_IP`, `IPPROTO_ICMP` 和 ICMP 的種類 `ICMP_ECHOREPLY` 則定義在 `bpf_tracing_net.h`。
ETH, IP 的部份,只需要檢查標頭中協定的類型,確認內層的協定屬於要處理的類型,繼續下一層協定的處理。接著作為被動方,XDP 要判斷 ICMP 封包類型,在接收到 `ICMP_ECHOREPLY` 的時候將其丟掉。
### 示範程式碼
參數類別是 `struct xdp_md *` ,封包的記憶體位置紀錄在 `data`, `data_end` ,範圍是 $[\text{data, data_end})$ ,不包含 `data_end`
```clike
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include "bpf_tracing_net.h"
SEC("xdp")
int drop_ping(struct xdp_md *ctx) {
void *data = (void *)(__u64)ctx->data;
void *data_end = (void *)(__u64)ctx->data_end;
struct ethhdr *l2 = data;
// 和 data_end 比較避免越界存取
...
return XDP_PASS;
}
```
### eBPF 物件的生命週期
eBPF 的程式和 map 使用引用計數(reference counting)進行資源管理。Pinning eBPF 物件, 引用 maps,附著程式(建立 links)可以增加引用計數。
- Pinning:建立一個 pseudofile
- links:函式庫對「附著程式」的抽象
### `bpftool` 操作介紹
用戶程式除了附著程式以外沒有其他功能時,可以使用 `bpftool` 用命令的方式操作,避免用戶程式重複開發。
#### 將程式載入核心
``` shell
$ bpftool prog load hello.bpf.o /sys/fs/bpf/hello
```
此處,` bpftool prog load ` 指令會載入已編譯的 eBPF 程式,並將它綁定(pinning)到指定的路徑(在此範例中為 ` /sys/fs/bpf/hello `)。
#### 檢查已載入的程式
``` shell
$ ls /sys/fs/bpf
hello
```
``` shell
$ bpftool prog list
...
540: xdp name hello tag d35b94b4c0c10efb gpl
loaded_at 2022-08-02T17:39:47+0000 uid 0
xlated 96B jited 148B memlock 4096B map_ids 165,166
btf_id 254
```
輸出顯示了 id 為 540 的程式,類型為 xdp,名稱為 hello,並包含 id 為 165 和 166 的關聯 map。
可以使用這些欄位來查找特定的 eBPF 程式或 map,例如:
- ` $ bpftool prog show id 540 `
- ` $ bpftool map show id 165 `
#### 附著到事件
程式類型必須與所附著的事件類型匹配。例如,可以將 XDP 程式附著到特定網路接口上的 XDP 事件:
``` shell
$ bpftool net attach xdp id 540 dev eth0
```
查看所有附著到網路的 eBPF 程式:
``` shell
$ bpftool net list
xdp:
eth0(2) driver id 540
tc:
flow_dissector:
```
#### 分離與卸載程式
分離程式:
``` shell
$ bpftool net detach xdp dev eth0
```
刪除 pinning:
``` shell
$ rm /sys/fs/bpf/hello
```
#### 相關 API
- `bpf_htons()`
### 目標一:練習項目
- 撰寫 XDP 核心程式
- 使用 `bpftool` 加載和附著程式
- 測試程式運作。在執行 `ping` 的過程附著,應從正常回應變成沒有回應
## 目標二:sockops
透過本機通訊的封包,發送和接收的時候都經過核心網路協定層的處理。 eBPF 可以縮短封包傳遞路徑,避免花費非必要的成本。

### eBPF 程式類型介紹
需要兩個類別 eBPF 程式,程式會分為兩個檔案實作:
- sockops :在多處觸發,透過 `struct bpf_sock_ops` 參數中 `ops` 的欄位判斷觸發情境。大致可分為兩種:
- **進行配置修改**:如 `BPF_SOCK_OPS_TIMEOUT_INIT`
- **TCP 狀態改變時,觸發 callback**:如 `BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB` 或 `BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB`
- sk_msg :儲存在 map 中的 socket 發送封包時觸發,標註 socket 指定後續該轉傳到特定 socket 或維持正常操作。
### 處理流程

### 示範程式碼
這次使用特殊的 map ,定義在 *bpf_sockmap.h* ,鍵的欄位紀錄來源和目標的資訊就足夠
*bpf_sockmap.h*
```clike
#include "vmlinux.h"
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
struct sock_key {
__u32 sip;
__u32 dip;
__u32 sport;
__u32 dport;
__u32 family;
};
struct {
__uint(type, BPF_MAP_TYPE_SOCKHASH);
__uint(max_entries, 65535);
__type(key, struct sock_key);
__type(value, int);
} sock_ops_map SEC(".maps");
```
在 sockops 的部分
```clike
#include "bpf_sockmap.h"
char LICENSE[] SEC("license") = "Dual BSD/GPL";
SEC("sockops")
int bpf_sockops_handler(struct bpf_sock_ops *skops){
// skops->op 判斷是否為 BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB/BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB
// skops->remote_ip4, skops->local_ip4 判斷是否為 127.0.0.1
struct sock_key key = ...
bpf_sock_hash_update(skops, &sock_ops_map, &key, BPF_NOEXIST);
return BPF_OK;
}
```
sk_msg 的部分
```clike
#include "bpf_sockmap.h"
char LICENSE[] SEC("license") = "Dual BSD/GPL";
SEC("sk_msg")
int bpf_redir(struct sk_msg_md *msg)
{
struct sock_key key = ...
// 查詢 map ,找到的話會直接對 socket 標註
// 找到對應元素回傳 SK_PASS,否則回傳 SK_DROP
bpf_msg_redirect_hash(msg, &sock_ops_map, &key, BPF_F_INGRESS);
// 回傳 SK_DROP 會丟掉封包,
// 即使沒找到也希望能走正常傳輸路徑
// 所以一律回傳 SK_PASS
return SK_PASS;
}
```
### `bpftool` 操作介紹
這個目標我們需要加載兩個程式,然後 sockops 附著到 cgroup, sk_msg 附著到 map
#### 加載並重用 map
普通的 `bpftool prog load` 會自動創建 map ,如果需要重用的話,命令格式是:
```shell
$ bpftool prog help
usage: bpftool prog load OBJ PATH map name NAME MAP
...
MAP := { id MAP_ID | name MAP_NAME }
```
#### 附著 sockops
- `CGROUP` 使用 /sys/fs/cgroup/
- `ATTACH_TYPE` 使用 sock_ops
```shell
$ bpftool cgroup attach CGROUP ATTACH_TYPE PROG
```
#### 附著 sk_msg
- `ATTACH_TYPE` 使用 msg_verdict
```shell
$ bpftool prog attach PROG ATTACH_TYPE MAP
```
#### 分離與卸載程式
分離程式:
```shell
$ bpftool prog detach PROG ATTACH_TYPE MAP
```
```shell
$ bpftool cgroup detach CGROUP ATTACH_TYPE PROG
```
刪除相關 pinning
### 目標二:練習項目
- 撰寫 sockops、sk_msg 核心程式
- 使用 `bpftool` 加載和附著程式
- 測試程式運作。用 `tcpdump` 進行監聽,只會出現連線建立和結束的封包訊息