{%hackmd theme-dark %} # VPP & DPDK L3 Forwarding --- 首先要提到我為什麼要回來看 `DPDK` 呢? 經過這幾天的 `Survey` 我大致上了解了 `VPP` 與 `DPDK` 兩者扮演的角色 可以把 `VPP` 當成是套件管理工具 而 `DPDK` 則是==撰寫套件==的工具 --- ## VPP ![](https://i.imgur.com/rUpEyjn.png) <!-- 先從 `VPP` 談起 透過將傳入的 `Packet` 組成 `Packet Vector` 並遍歷節點圖,並做對應的處置 此做法能有效降低高頻寬 `i-cache` 未命中頻繁發生 浪費大量 `CPU` 時間 `refetch` 指令而導致的效能損耗 --> --- ![](https://i.imgur.com/N0USCTW.png) <!-- Plugins:主要爲實現一些功能,在程序啓動的時候加載,一般情況下會在插件中加入一些node節點去實現相關功能 Vnet:提供網絡資源能力:比如設備,L2,L3,L4功能,session管理,控制管理,流量管理等 VLIB:主要提供基本的應用管理庫:buffer管理,graph node管理,線程,CLI,trace等 VPP Infra:提供一些基本的通用的功能函數庫:包括內存管理,向量操作,hash, timer等 --> --- ## DPDK ![](https://i.imgur.com/jL4p7jU.png) <!-- 那 `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}]"}
    1151 views