eBPF/XDP対応UPF - eUPFインストール

eBPFは、Berkeley Packet Filter(BPF)の後継として、OSのkernelなどの特権コンテキストでプログラムを実行する技術です。kernelのソースコードの変更やmoduleの読み込みを必要とせずに、実行時にkernelの機能を安全かつ効率的に拡張するために使用されます。また、XDP(eXpress Data Path)は、OSのネットワークスタックの大部分をバイパスして、packetを高速で送受信するためのeBPFベースの高性能データパスです。
ここでは、モバイルネットワークを構成するU-PlaneのUPF(5G)ならびにPGW-U(4G)にeBPF/XDPを適用した[eUPF](https://github.com/edgecomllc/eupf)をホストOSでネイティブにビルド、インストールする手順と構成について簡単に説明します。eUPFにより、U-Planeのデータネットワークの高速化を図れると期待しています。
**なお、本記事は[Open5GS](https://github.com/open5gs/open5gs)および[free5GC](https://github.com/free5gc/free5gc)でeUPFを使用するための準備として書きました。**

## eUPFとData Network Gatewayの簡単な概要

U-Planeを中心としたeUPFとData Network Gatewayの簡単な構成について説明します。**この構成は Virtualbox VMに構築されることに注意して下さい。** このため、仮想NICでは性能が出ないことが考えられますが、本記事が物理NICを使用する際の設定の参考になれればと思います。
このシミュレーション環境は以下の条件を満たす最小構成になります。

- 1つのUPFとData Network Gateway

構築したシミュレーション環境は以下の通りです。

<img width="800" alt="network-overview.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/457466/355cb252-edbe-55c2-7805-92c8e05fc5c5.png">

使用したeUPFは以下になります。

- eBPF/XDP UPF - eUPF v0.6.1 (2024.01.25) - https://github.com/edgecomllc/eupf

各VMは以下の通りです。

| VM | SW & Role | IP address | OS | CPU<br>(Min) | Memory<br>(Min) | HDD<br>(Min) |
| --- | --- | --- | --- | --- | --- | --- |
| VM-UP | eUPF U-Plane | 192.168.0.151/24 | Ubuntu 22.04 | 1 | 2GB | 20GB |
| VM-DN | Data Network Gateway | 192.168.0.152/24 | Ubuntu 22.04 | 1 | 1GB | 10GB |

各VMのネットワークインターフェースは以下の通りです。

| VM | Device | Network Adapter | IP address | Interface | XDP |
| --- | --- | --- | --- | --- | --- |
| VM-UP | ~~enp0s3~~ | ~~NAT(default)~~ | ~~10.0.2.15/24~~ | ~~(VM default NW)~~ ***down*** | -- |
| | enp0s8 | Bridged Adapter | 192.168.0.151/24 | (Mgmt NW) | -- |
| | enp0s9 | NAT Network | 192.168.13.151/24 | N3 | x |
| | enp0s10 | NAT Network | 192.168.14.151/24 | N4 | -- |
| | enp0s16 | NAT Network | 192.168.16.151/24 | N6 | x |
| VM-DN | enp0s3 | NAT(default) | 10.0.2.15/24 | (VM default NW) | -- |
| | enp0s8 | Bridged Adapter | 192.168.0.152/24 | (Mgmt NW) | -- |
| | enp0s9 | NAT Network | 192.168.16.152/24 | N6, ***default GW for VM-UP*** | -- |

5GのNF間のインターフェースの内、N3、N4、N6については、VirtualboxのNATネットワークで以下の通りに設定しました。

| Network Name | Network CIDR |
| --- | --- |
| N3 | 192.168.13.0/24 |
| N4 | 192.168.14.0/24 |
| N6 | 192.168.16.0/24 |

**Virtualbox GUIツールは、1つのVMに最大4つのネットワークアダプタを登録できます。なお、VM-UPにはネットワークアダプタを5つ登録するため、GUIツールでは残り1つを登録できません。この場合、次のようにvboxファイルを直接編集し、残りのネットワークアダプタを登録します。**

**例)**

```diff
--- eupf10.vbox.orig    2023-10-28 21:23:38.808738418 +0900
+++ eupf10.vbox 2023-10-29 06:41:01.157520495 +0900
@@ -68,7 +68,12 @@
           </DisabledModes>
           <NATNetwork name="N4"/>
         </Adapter>
-        <Adapter slot="8" MACAddress="080027FE0AA2" cable="false"/>
+        <Adapter slot="8" enabled="true" MACAddress="080027FE0AA2" type="82540EM">
+          <DisabledModes>
+            <InternalNetwork name="intnet"/>
+          </DisabledModes>
+          <NATNetwork name="N6"/>
+        </Adapter>
         <Adapter slot="9" MACAddress="080027990637" cable="false"/>
         <Adapter slot="10" MACAddress="080027AC33FC" cable="false"/>
         <Adapter slot="11" MACAddress="08002748BC3D" cable="false"/>
```

## VM-UPにeUPFを構築

eUPFの構築については以下を参照して下さい。

- eUPF v0.6.1 (2024.01.25) - https://github.com/edgecomllc/eupf

### 必要なパッケージをインストール

```
# apt install git clang llvm gcc-multilib libbpf-dev
```

### Golangインストール

```
# wget https://go.dev/dl/go1.20.10.linux-amd64.tar.gz
# tar -C /usr/local -zxvf go1.20.10.linux-amd64.tar.gz
# mkdir -p ~/go/{bin,pkg,src}
# echo 'export GOPATH=$HOME/go' >> ~/.bashrc
# echo 'export GOROOT=/usr/local/go' >> ~/.bashrc
# echo 'export PATH=$PATH:$GOPATH/bin:$GOROOT/bin' >> ~/.bashrc
# echo 'export GO111MODULE=auto' >> ~/.bashrc
# source ~/.bashrc
```

### GolangのためのSwagコマンドラインツールをインストール

```
# go install github.com/swaggo/swag/cmd/swag@v1.8.12
```

### eUPFをgit clone

```
# git clone https://github.com/edgecomllc/eupf.git
# cd eupf
```

### コード生成を実行

```
# go generate -v ./cmd/...
```
デバッグ用にkernelログを出力したい場合は、以下のオプションを追加して実行して下さい。
```
# BPF_CFLAGS="-DENABLE_LOG" go generate -v ./cmd/...
```
これにより、以下のようにeBPFプログラムからkernelログを参照することができます。
```
# cat /sys/kernel/debug/tracing/trace_pipe
```

### eUPFをビルド

```
# go build -v -o bin/eupf ./cmd/
```

## VM-UPにeUPFをセットアップ

eUPFのセットアップについては、以下を参照して下さい

- eUPF v0.6.1 (2024.01.25) - https://github.com/edgecomllc/eupf/blob/main/docs/Configuration.md

まず、`/etc/sysctl.conf`ファイルの次の行のコメントを解除し、OSに反映させます。
```
net.ipv4.ip_forward=1
```
```
# sysctl -p
```
次に、VM-UPのデフォルトインターフェース`enp0s3`を落とし、デフォルトGWをN6インターフェース`enp0s16`のVM-DN IPアドレスに設定します。
```
# ip link set dev enp0s3 down
# ip route add default via 192.168.16.152 dev enp0s16
```

### 設定ファイルを作成

`/root/eupf`ディレクトリを作成し、設定ファイルを配置します。

- `/root/eupf/config.yml`

```yaml
interface_name: [enp0s9, enp0s16]
xdp_attach_mode: generic
api_address: :8080
pfcp_address: 192.168.14.151:8805
pfcp_node_id: 192.168.14.151
metrics_address: :9090
n3_address: 192.168.13.151
gtp_peer:
echo_interval: 10
qer_map_size: 1024
far_map_size: 1024
pdr_map_size: 1024
resize_ebpf_maps: false
heartbeat_retries: 3
heartbeat_interval: 5
heartbeat_timeout: 5
logging_level: info
feature_ueip: true
feature_ftup: true
ueip_pool: 10.45.0.0/16
teid_pool: 65536
```
上記の設定ファイルでは、`xdp_attach_mode`を`generic`(kernelレベル実装)にしていますが、この場合、性能は期待できません。eBPF/XDP機能のデバック用と捉えて下さい。`native`(driverレベル実装)や`offload`(NICレベル実装)の場合、性能が期待できます。参考までに、XDPに対応するdriverの一覧は[こちら](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md#xdp)にあります。

## VM-UPでeUPFを実行

```
# cd /root/eupf
# bin/eupf --config config.yml
2024/02/11 21:08:49 Startup config: map[api_address::8080 echo_interval:10 far_map_size:1024 feature_ftup:true feature_ueip:true gtp_peer:[] heartbeat_interval:5 heartbeat_retries:3 heartbeat_timeout:5 interface_name:[enp0s9 enp0s16] logging_level:info metrics_address::9090 n3_address:192.168.13.151 pdr_map_size:1024 pfcp_address:192.168.14.151:8805 pfcp_node_id:192.168.14.151 qer_map_size:1024 resize_ebpf_maps:false teid_pool:65536 ueip_pool:10.45.0.0/16 xdp_attach_mode:generic]
2024/02/11 21:08:49 Apply eUPF config: {InterfaceName:[enp0s9 enp0s16] XDPAttachMode:generic ApiAddress::8080 PfcpAddress:192.168.14.151:8805 PfcpNodeId:192.168.14.151 MetricsAddress::9090 N3Address:192.168.13.151 GtpPeer:[] EchoInterval:10 QerMapSize:1024 FarMapSize:1024 PdrMapSize:1024 EbpfMapResize:false HeartbeatRetries:3 HeartbeatInterval:5 HeartbeatTimeout:5 LoggingLevel:info UEIPPool:10.45.0.0/16 FTEIDPool:65536 FeatureUEIP:true FeatureFTUP:true}
2024/02/11 21:08:49 INF Attached XDP program to iface "enp0s9" (index 2)
2024/02/11 21:08:49 INF Attached XDP program to iface "enp0s16" (index 4)
2024/02/11 21:08:49 INF Initialize resources: UEIP pool (CIDR: "10.45.0.0/16"), TEID pool (size: 65536)
2024/02/11 21:08:49 INF Starting PFCP connection: 192.168.14.151:8805 with Node ID: 192.168.14.151 and N3 address: 192.168.13.151
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /api/v1/health            --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).InitRoutes.func1 (4 handlers)
[GIN-debug] GET    /api/v1/xdp_stats         --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).displayXdpStatistics-fm (4 handlers)
[GIN-debug] GET    /api/v1/packet_stats      --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).displayPacketStats-fm (4 handlers)
[GIN-debug] GET    /api/v1/config            --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).displayConfig-fm (4 handlers)
[GIN-debug] POST   /api/v1/config            --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).editConfig-fm (4 handlers)
[GIN-debug] GET    /api/v1/uplink_pdr_map/:id --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).getUplinkPdrValue-fm (4 handlers)
[GIN-debug] PUT    /api/v1/uplink_pdr_map/:id --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).setUplinkPdrValue-fm (4 handlers)
[GIN-debug] GET    /api/v1/qer_map           --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).listQerMapContent-fm (4 handlers)
[GIN-debug] GET    /api/v1/qer_map/:id       --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).getQerValue-fm (4 handlers)
[GIN-debug] PUT    /api/v1/qer_map/:id       --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).setQerValue-fm (4 handlers)
[GIN-debug] GET    /api/v1/far_map/:id       --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).getFarValue-fm (4 handlers)
[GIN-debug] PUT    /api/v1/far_map/:id       --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).setFarValue-fm (4 handlers)
[GIN-debug] GET    /api/v1/pfcp_associations --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).listPfcpAssociations-fm (4 handlers)
[GIN-debug] GET    /api/v1/pfcp_associations/full --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).listPfcpAssociationsFull-fm (4 handlers)
[GIN-debug] GET    /api/v1/pfcp_sessions     --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).listPfcpSessionsFiltered-fm (4 handlers)
[GIN-debug] GET    /swagger/*any             --> github.com/swaggo/gin-swagger.CustomWrapHandler.func1 (4 handlers)
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /metrics                  --> github.com/edgecomllc/eupf/cmd/api/rest.(*ApiHandler).InitMetricsRoute.func1.1 (4 handlers)
2024/02/11 21:08:49 INF running on :8080
2024/02/11 21:08:49 INF running on :9090
```

## VM-DNでData Network Gatewayを設定

まず、`/etc/sysctl.conf`ファイルの次の行のコメントを解除し、OSに反映させます。
```
net.ipv4.ip_forward=1
```
```
# sysctl -p
```
次に、NAPTとeUPFのN6 IPアドレスへのルーティングを設定します。
```
# iptables -t nat -A POSTROUTING -s <DN> -j MASQUERADE
# ip route add <DN> via 192.168.16.151 dev enp0s9
```
**コア網に応じて`<DN>`を設定します。  
例) `10.45.0.0/16`**

以上で、eUPFの構築が完了しました。これにより、eUPFをOpen5GSおよびfree5GCのC-Planeと連携できるようになります。

## 最後に

eUPF(eBPF/XDP UPF)を試したのは、v0.5.1(2023.10.21)からでした。この時点では、`PFCP Association Setup`において、eUPFは`FTUP`に未対応で、`User Plane IP Resource Information`を使用してIPアドレスを割り当てるしかありませんでした。ただ、3GPP Release 16.3でこの機能は削除されたため、結果、R.17対応のOpen5GSではN3、N4のIPアドレスを適切に分離することができませんでした。一方、free5GCはR.15対応と古いため、`User Plane IP Resource Information`を使用してN3、N4のIPアドレスを適切に分離できますが、最新規格で有効なFTUPを使用するに越したことはありません。このことを、eUPF開発者に伝えたところ、v0.6.0(2023.12.06)で`FTUP`に対応して頂きました。

eBPF/XDPによるUPF性能の恩恵を受けるには、`native`モードでnetwork driverのXDP支援機能を利用するか、`offload`モードでNICのXDP支援機能を利用する必要があります。なお、`generic`モードの場合、性能は期待できず、あくまでもeBPF/XDP機能の検証という位置付けで捉えて下さい。

ご参考までに、eUPFを5GのUPFとして使用する設定例は以下の通りです。

- [Open5GS 5GC & UERANSIM UE / RAN Sample Configuration - eUPF(eBPF/XDP UPF)](https://github.com/s5uishida/open5gs_5gc_ueransim_eupf_sample_config)
- [free5GC 5GC & UERANSIM UE / RAN Sample Configuration - eUPF(eBPF/XDP UPF)](https://github.com/s5uishida/free5gc_ueransim_eupf_sample_config)

また、4GのPGW-Uとして使用する設定例は以下の通りです。

- [Open5GS EPC & srsRAN 4G with ZeroMQ UE / RAN Sample Configuration - eUPF(eBPF/XDP UPF(PGW-U))](https://github.com/s5uishida/open5gs_epc_srsran_eupf_sample_config)

最後に、元記事は[Github](https://github.com/s5uishida/install_eupf)に書いたものです。

## 主な変更履歴

- [2024.02.14] 初版。