dpdk收包小程序
前提:
1、环境以及装好dpdk并接管网卡;
2、网卡有接入流量;
3、编译方式:自己写makefile或者在装有dpdk的环境上gcc;
4、运行方式:./mycap -l 4 -n 4 --file-prefix=mycap --huge-dir=/dev/hugepages -- -p 0
/* * 功能: * 1) 启动时打印 EAL 进程类型 (PRIMARY / SECONDARY), 用来确认其他dpdk处理程序是否已退出 * 2) 列出当前 DPDK 可见的所有端口 * 3) 从指定端口收包, 逐包打印【包长度】(不打印内容) * 4) 包内容落到 2048 字节定长缓冲区 g_pkt_buf, 供后续处理 * * 编译: 见同目录 Makefile * 运行: ./mycap -l 4 --file-prefix=mycap --huge-dir=/dev/hugepages -p 4 */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <inttypes.h> #include <string.h> #include <signal.h> #include <time.h> #include <rte_eal.h> #include <rte_errno.h> #include <rte_ethdev.h> #include <rte_mbuf.h> #include <rte_ether.h> #include <rte_dev.h> /* ============================ 可调参数 ============================ */ #define RX_BURST 32 /* 每次 rx_burst 最多取多少个 mbuf */ #define RX_DESC_NUM 1024 /* RX 描述符数量 */ #define TX_DESC_NUM 1024 /* TX 描述符数量 (只收不发, 仅为满足驱动要求) */ #define MBUF_POOL_SIZE 8192 /* mbuf 池元素个数 */ #define MBUF_CACHE_SIZE 256 /* 每核 mbuf 缓存大小 */ /* 包内容落到的定长缓冲区大小 (题目要求 2048) */ #define PKT_COPY_SIZE 2048 /* ============================ 全局状态 ============================ */ static volatile sig_atomic_t g_stop = 0; /* 包内容缓冲区: 保留最近一个包的前 2048 字节, 不打印 */ static char g_pkt_buf[PKT_COPY_SIZE]; static uint32_t g_pkt_copy_len = 0; static uint64_t g_rx_pkts = 0; static uint64_t g_rx_bytes = 0; static int g_quiet = 0; /* -q: 不逐包打印长度, 只每秒打印汇总 */ /* ============================ 辅助函数 ============================ */ static void on_signal(int sig) { (void)sig; g_stop = 1; } static void usage(const char *prog) { printf("用法: %s <EAL 参数...> [-p <port_id>] [-q] [--mycap-help]\n", prog); printf("\n" " 本程序自己的参数 (必须写在 EAL 参数之后):\n" " -p <port_id> 指定收包端口 (默认 0, 具体值看启动时打印的端口列表)\n" " -q 安静模式: 不逐包打印长度, 只每秒打印一次汇总\n" " --mycap-help 显示本帮助\n" " 注意: -h 会被 EAL 自己吃掉 (打印 EAL 的参数列表), 所以这里另起名字。\n" "\n" " 常用 EAL 参数:\n" " -l 4 只使用 lcore 4\n" " --file-prefix=mycap 共享内存前缀\n" " --huge-dir=/dev/hugepages\n" " --proc-type=auto 让 EAL 自动判定 primary / secondary\n" "\n" " 完整示例:\n" " %s -l 4 --file-prefix=mycap --huge-dir=/dev/hugepages -p 4\n", prog); } static void list_ports(void) { uint16_t port_id; printf("=== 当前 DPDK 可见端口 ===\n"); RTE_ETH_FOREACH_DEV(port_id) { struct rte_eth_dev_info info; struct rte_ether_addr mac; char mac_buf[RTE_ETHER_ADDR_FMT_SIZE]; if (rte_eth_dev_info_get(port_id, &info) != 0) continue; if (rte_eth_macaddr_get(port_id, &mac) == 0) rte_ether_format_addr(mac_buf, sizeof(mac_buf), &mac); else snprintf(mac_buf, sizeof(mac_buf), "unknown"); printf(" port %-2u mac=%-18s driver=%-12s socket=%d rxq_max=%u txq_max=%u\n", port_id, mac_buf, info.driver_name ? info.driver_name : "?", rte_eth_dev_socket_id(port_id), info.max_rx_queues, info.max_tx_queues); } printf("==========================\n"); } /* 配置并启动端口: 1 个 RX 队列 + 1 个 TX 队列 */ static int setup_port(uint16_t port_id, struct rte_mempool *mp) { struct rte_eth_conf port_conf; struct rte_eth_dev_info dev_info; uint16_t nb_rxd = RX_DESC_NUM; uint16_t nb_txd = TX_DESC_NUM; int socket_id; int ret; memset(&port_conf, 0, sizeof(port_conf)); ret = rte_eth_dev_info_get(port_id, &dev_info); if (ret != 0) { fprintf(stderr, "port %u: 获取设备信息失败: %s\n", port_id, rte_strerror(-ret)); return -1; } socket_id = rte_eth_dev_socket_id(port_id); if (socket_id < 0) socket_id = (int)SOCKET_ID_ANY; /* 描述符数量按硬件实际上限调整 */ ret = rte_eth_dev_adjust_nb_rx_tx_desc(port_id, &nb_rxd, &nb_txd); if (ret != 0) { fprintf(stderr, "port %u: 调整描述符数量失败: %s\n", port_id, rte_strerror(-ret)); return -1; } ret = rte_eth_dev_configure(port_id, 1, 1, &port_conf); if (ret < 0) { fprintf(stderr, "port %u: rte_eth_dev_configure 失败: %s\n", port_id, rte_strerror(-ret)); return -1; } ret = rte_eth_rx_queue_setup(port_id, 0, nb_rxd, socket_id, NULL, mp); if (ret < 0) { fprintf(stderr, "port %u: RX 队列设置失败: %s\n", port_id, rte_strerror(-ret)); return -1; } ret = rte_eth_tx_queue_setup(port_id, 0, nb_txd, socket_id, NULL); if (ret < 0) { fprintf(stderr, "port %u: TX 队列设置失败: %s\n", port_id, rte_strerror(-ret)); return -1; } ret = rte_eth_dev_start(port_id); if (ret < 0) { fprintf(stderr, "port %u: 启动失败: %s\n", port_id, rte_strerror(-ret)); return -1; } /* 混杂模式: 收下该口全部流量 */ rte_eth_promiscuous_enable(port_id); printf("port %u: RX 队列 0 (%u 描述符) / TX 队列 0 (%u 描述符) 已启动, 混杂模式已开启\n", port_id, nb_rxd, nb_txd); return 0; } /* ============================ 收包主循环 ============================ */ static void rx_loop(uint16_t port_id) { struct rte_mbuf *bufs[RX_BURST]; time_t last_time; uint64_t last_pkts; printf("开始在 port %u 收包 (Ctrl-C 退出)\n", port_id); if (!g_quiet) printf("逐包打印格式: pkt len: <长度> (统计信息走 stderr)\n"); last_time = time(NULL); last_pkts = 0; while (!g_stop) { uint16_t nb_rx = rte_eth_rx_burst(port_id, 0, bufs, RX_BURST); uint16_t i; time_t now; if (nb_rx == 0) continue; /* 忙等 */ for (i = 0; i < nb_rx; i++) { uint32_t len = rte_pktmbuf_pkt_len(bufs[i]); /* ---- 把包内容落到 2048 字节定长缓冲区 (不打印) ---- */ g_pkt_copy_len = (len < PKT_COPY_SIZE) ? len : (PKT_COPY_SIZE - 1); if (g_pkt_copy_len > 0) { const void *src = rte_pktmbuf_read(bufs[i], 0, g_pkt_copy_len, g_pkt_buf); /* 数据都在首段时 rte_pktmbuf_read 直接返回 mbuf 内部指针, * 不会写入 g_pkt_buf, 这里补一次拷贝 */ if (src != g_pkt_buf) memcpy(g_pkt_buf, src, g_pkt_copy_len); } g_pkt_buf[g_pkt_copy_len] = '\0'; /* ---- 到这里 g_pkt_buf / g_pkt_copy_len 已就绪, 后续处理写在这里 ---- */ #ifdef DEBUG_DUMP /* 调试用: 打印缓冲区头 64 字节, 用 EXTRA_CFLAGS=-DDEBUG_DUMP 打开 */ { uint32_t k; fprintf(stderr, "[dump] copylen=%u head=", g_pkt_copy_len); for (k = 0; k < 64 && k < g_pkt_copy_len; k++) fprintf(stderr, "%02x", (unsigned char)g_pkt_buf[k]); fprintf(stderr, "\n"); } #endif if (!g_quiet) printf("pkt len: %u\n", len); g_rx_pkts++; g_rx_bytes += len; rte_pktmbuf_free(bufs[i]); /* 必须还池, 否则很快耗尽 */ } /* 每秒打印一次汇总 (走 stderr, 便于把 stdout 重定向到文件) */ now = time(NULL); if (now != last_time) { fprintf(stderr, "[stats] 累计 %" PRIu64 " 包 / %" PRIu64 " 字节, 上一秒 %" PRIu64 " pps\n", g_rx_pkts, g_rx_bytes, g_rx_pkts - last_pkts); last_time = now; last_pkts = g_rx_pkts; } } } /* ================================ main ================================ */ int main(int argc, char **argv) { struct rte_mempool *mp; uint16_t port_id = 0; enum rte_proc_type_t proc_type; int ret; int i; /* 1. EAL 初始化 */ ret = rte_eal_init(argc, argv); if (ret < 0) rte_exit(EXIT_FAILURE, "EAL 初始化失败\n"); /* rte_eal_init 返回它消费掉的参数个数, 剩下的才是我们自己的 */ argc -= ret; argv += ret; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-q") == 0) { g_quiet = 1; } else if (strcmp(argv[i], "-p") == 0) { if (i + 1 >= argc) { fprintf(stderr, "-p 后面需要跟端口号\n"); return EXIT_FAILURE; } port_id = (uint16_t)atoi(argv[++i]); } else if (strcmp(argv[i], "--mycap-help") == 0) { usage(argv[0]); return EXIT_SUCCESS; } else { fprintf(stderr, "未知参数: %s\n", argv[i]); usage(argv[0]); return EXIT_FAILURE; } } /* 2. 打印进程类型 —— 用来确认 primary 名额是否已释放 */ proc_type = rte_eal_process_type(); printf("EAL 进程类型: %s\n", proc_type == RTE_PROC_PRIMARY ? "PRIMARY" : "SECONDARY"); if (proc_type != RTE_PROC_PRIMARY) { fprintf(stderr, "警告: 当前是 SECONDARY。说明已有 primary 进程占着这个 DPDK 实例\n" " (很可能是其他dpdk处理程序还在运行)。请先确认:\n" " pgrep -a dpdk\n" " 若确实要用 --proc-type=auto 抢占, 请确保其他dpdk处理程序已完全退出。\n"); } signal(SIGINT, on_signal); signal(SIGTERM, on_signal); /* 3. 列出端口并校验目标端口 */ list_ports(); if (!rte_eth_dev_is_valid_port(port_id)) { fprintf(stderr, "端口 %u 无效, 请用 -p 指定上面列出的端口号\n", port_id); return EXIT_FAILURE; } /* 4. 建 mbuf 池 * 注意: 这里 priv_size = 0, 元素尾部不像其他dpdk处理程序那样内嵌 Packet 结构, * 因为本程序只当裸缓冲区用。 */ mp = rte_pktmbuf_pool_create("mycap_pool", MBUF_POOL_SIZE, MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id()); if (mp == NULL) rte_exit(EXIT_FAILURE, "创建 mbuf 池失败: %s\n", rte_strerror(rte_errno)); /* 5. 配置并启动端口 */ if (setup_port(port_id, mp) != 0) return EXIT_FAILURE; /* 6. 收包 */ rx_loop(port_id); /* 7. 收尾 */ printf("\n收到退出信号, 正在收尾...\n"); rte_eth_dev_stop(port_id); rte_eth_dev_close(port_id); printf("总计收到 %" PRIu64 " 包 / %" PRIu64 " 字节\n", g_rx_pkts, g_rx_bytes); return EXIT_SUCCESS; }

浙公网安备 33010602011771号