{%hackmd theme-dark %}
# VPP & DPDK L3 Forwarding
---
首先要提到我為什麼要回來看 `DPDK` 呢?
經過這幾天的 `Survey`
我大致上了解了 `VPP` 與 `DPDK` 兩者扮演的角色
可以把 `VPP` 當成是套件管理工具
而 `DPDK` 則是==撰寫套件==的工具
---
## VPP

<!--
先從 `VPP` 談起
透過將傳入的 `Packet` 組成 `Packet Vector`
並遍歷節點圖,並做對應的處置
此做法能有效降低高頻寬 `i-cache` 未命中頻繁發生
浪費大量 `CPU` 時間 `refetch` 指令而導致的效能損耗
-->
---

<!--
Plugins:主要爲實現一些功能,在程序啓動的時候加載,一般情況下會在插件中加入一些node節點去實現相關功能
Vnet:提供網絡資源能力:比如設備,L2,L3,L4功能,session管理,控制管理,流量管理等
VLIB:主要提供基本的應用管理庫:buffer管理,graph node管理,線程,CLI,trace等
VPP Infra:提供一些基本的通用的功能函數庫:包括內存管理,向量操作,hash, timer等
-->
---
## DPDK

<!--
那 `DPDK` 透過撰寫在 `kernel level` 運行的 `PMD`
取代原來的中斷形式,建立 `ring buffer`
將 `kernel space` 的 `memory` 映射到 `user space`
配合各種 `heuristic algorithm`加速封包 `en-decapsulation`
`prefix match` 等處理速度
-->
---
### Code Review
首先看到 `main.c` 內建立變數與結構體
```cpp
struct lcore_conf *qconf;
struct rte_eth_dev_info dev_info;
struct rte_eth_txconf *txconf;
int ret;
unsigned nb_ports;
uint16_t queueid, portid;
unsigned lcore_id;
uint32_t n_tx_queue, nb_lcores;
uint8_t nb_rx_queue, queue, socketid;
```
---
其中 `rte` 開頭的結構皆來自 `library`
只有 `lcore_conf` 定義於 `l3fwd.h`
```cpp
struct lcore_conf {
uint16_t n_rx_queue;
struct lcore_rx_queue rx_queue_list[MAX_RX_QUEUE_PER_LCORE];
uint16_t n_tx_port;
uint16_t tx_port_id[RTE_MAX_ETHPORTS];
uint16_t tx_queue_id[RTE_MAX_ETHPORTS];
struct mbuf_table tx_mbufs[RTE_MAX_ETHPORTS];
void *ipv4_lookup_struct;
void *ipv6_lookup_struct;
} __rte_cache_aligned;
```
---
接著初始化 `EAL` 環境
```cpp
/* init EAL */
ret = rte_eal_init(argc, argv);
if (ret < 0)
rte_exit(EXIT_FAILURE, "Invalid EAL parameters\n");
argc -= ret;
argv += ret;
force_quit = false;
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
```
---
初始化目標 `MACs`,讀取參數後會覆蓋
```cpp
/* pre-init dst MACs for all ports to 02:00:00:00:00:xx */
for (portid = 0; portid < RTE_MAX_ETHPORTS; portid++) {
dest_eth_addr[portid] =
ETHER_LOCAL_ADMIN_ADDR + ((uint64_t)portid << 40);
*(uint64_t *)(val_eth + portid) = dest_eth_addr[portid];
}
```
---
讀取執行參數
```cpp
/* parse application arguments (after the EAL ones) */
ret = parse_args(argc, argv);
if (ret < 0)
rte_exit(EXIT_FAILURE, "Invalid L3FWD parameters\n");
if (check_lcore_params() < 0)
rte_exit(EXIT_FAILURE, "check_lcore_params failed\n");
ret = init_lcore_rx_queues();
if (ret < 0)
rte_exit(EXIT_FAILURE, "init_lcore_rx_queues failed\n");
nb_ports = rte_eth_dev_count_avail();
if (check_port_config() < 0)
rte_exit(EXIT_FAILURE, "check_port_config failed\n");
nb_lcores = rte_lcore_count();
```
---
根據參數決定建立 `Hash` 或 `LPM` 對照表
```cpp
/* Setup function pointers for lookup method. */
setup_l3fwd_lookup_tables();
```
---
初始化對應的 `ports`
```cpp
/* initialize all ports */
RTE_ETH_FOREACH_DEV(portid) {
struct rte_eth_conf local_port_conf = port_conf;
/* skip ports that are not enabled */
if ((enabled_port_mask & (1 << portid)) == 0) {
printf("\nSkipping disabled port %d\n", portid);
continue;
}
/* init port */
...
```
---
為每個啟用的 `port` 對應到不同的 `MAC` 出口
```cpp
/*
* prepare src MACs for each port.
*/
ether_addr_copy(&ports_eth_addr[portid],
(struct ether_addr *)(val_eth + portid) + 1);
```
---
初始化封包緩衝區 `mbuf`
```cpp
/* init memory */
ret = init_mem(NB_MBUF);
if (ret < 0)
rte_exit(EXIT_FAILURE, "init_mem failed\n");
```
---
將核心 `lcore` 與 `port` 綁定
建立對應的 `TX` 與 `RX` `queue`
```cpp
/* init one TX queue per couple (lcore,port) */
queueid = 0;
for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
...
}
printf("\n");
for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
...
}
printf("\n");
```
---
啟動 `L3FWD`
```cpp
/* start ports */
RTE_ETH_FOREACH_DEV(portid) {
if ((enabled_port_mask & (1 << portid)) == 0) {
continue;
}
/* Start device */
ret = rte_eth_dev_start(portid);
if (ret < 0)
rte_exit(EXIT_FAILURE,
"rte_eth_dev_start: err=%d, port=%d\n",
ret, portid);
/*
* If enabled, put device in promiscuous mode.
* This allows IO forwarding mode to forward packets
* to itself through 2 cross-connected ports of the
* target machine.
*/
if (promiscuous_on)
rte_eth_promiscuous_enable(portid);
}
printf("\n");
...
/* launch per-lcore init on every lcore */
rte_eal_mp_remote_launch(l3fwd_lkp.main_loop, NULL, CALL_MASTER);
RTE_LCORE_FOREACH_SLAVE(lcore_id) {
if (rte_eal_wait_lcore(lcore_id) < 0) {
ret = -1;
break;
}
}
```
---
停止 `L3FWD`
```cpp
/* stop ports */
RTE_ETH_FOREACH_DEV(portid) {
if ((enabled_port_mask & (1 << portid)) == 0)
continue;
printf("Closing port %d...", portid);
rte_eth_dev_stop(portid);
rte_eth_dev_close(portid);
printf(" Done\n");
}
printf("Bye...\n");
```
---
大致上可以了解整體程式的架構與概念,搭配官網詳細服用可以更清楚了解每個函數的功能
整體看下來老師說改用 `VPP` 可以想成是
以 `VPP` 做管理,`DPDK` 做實現
舉例來說,以 `VPP` 已實現的功能插件加上 `DPDK` 撰寫的新插件結合,可以有效避免重複造輪子的行為(爾且造出來的輪子還很爛)。
{"metaMigratedAt":"2023-06-15T15:39:06.301Z","metaMigratedFrom":"Content","title":"VPP & DPDK L3 Forwarding","breaks":true,"contributors":"[{\"id\":\"0847c3ee-925b-482b-b7ef-951d848ce632\",\"add\":6387,\"del\":1358}]"}