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;
}

 

posted @ 2026-10-10 21:31  雪地飞行棋  阅读(4)  评论(0)    收藏  举报