DPDK 简易应用开发之路 1:数据包接收与解析

news2024/9/20 5:23:51

本机环境为 Ubuntu20.04 ,dpdk-stable-20.11.10

DPDK 应用基础

DPDK应用程序的一般处理流程如下:

初始化DPDK环境:调用rte_eal_init()初始化DPDK环境抽象层(EAL),设置运行时环境和配置。

配置和绑定网卡:使用rte_eth_dev_configure()配置网卡,包括队列、收发缓冲区等。绑定网卡到DPDK的驱动程序,以确保网卡被DPDK识别和控制。

分配内存:使用rte_mempool_create()创建内存池,用于存储数据包缓冲区。

设置接收和发送队列:使用rte_eth_rx_queue_setup()rte_eth_tx_queue_setup()设置接收和发送队列,分配相应的内存和配置参数。

启动网卡:调用rte_eth_dev_start()启动网卡,使其开始接收和发送数据包。

主处理循环:进入主循环,调用rte_eth_rx_burst()从接收队列中获取数据包。
处理数据包,执行所需的操作,如数据包转发、处理或其他逻辑。使用rte_eth_tx_burst()将处理后的数据包发送到发送队列。

清理资源:在应用程序退出前,调用rte_eth_dev_stop()停止网卡,释放资源,调用rte_mempool_free()释放内存池。

头文件

// DPDK
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_mbuf.h>
#include <rte_ether.h>
#include <rte_ip.h>
#include <rte_tcp.h>
#include <rte_udp.h>
//Linux
#include <linux/if_ether.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <stdint.h>
#include <stdio.h>

定义关键变量

#define RX_RING_SIZE 128  //发送环形缓冲区
#define NUM_MBUFS 8191  //数据包缓冲池
#define MBUF_CACHE_SIZE 0  //内存池中每个缓存的大小(以数据包为单位)
#define BURST_SIZE 32  //批量处理的大小

#define DPDK_QUEUE_ID_RX 0 // 接收队列ID

int g_dpdkPortId = -1;  //网络端口ID
static const struct rte_eth_conf port_conf_default = {
    .rxmode = { .max_rx_pkt_len = RTE_ETHER_MAX_LEN }
};

其中,g_dpdkPortId是我们要使用的网络端口,需要在下面初始化的时候找一个空闲的。rte_eth_conf是包含了各种配置选项,用于设置网卡的工作模式、队列、过滤器等(需要根据实际网卡来设置)。本文的simple app 只需要设置一下rx(接收队列)的设置即可。

端口初始化

static void port_init(struct rte_mempool *mbuf_pool) {
    // 查找空闲端口
    g_dpdkPortId = 0;
    while (g_dpdkPortId < RTE_MAX_ETHPORTS &&
	       rte_eth_devices[g_dpdkPortId].data->owner.id != RTE_ETH_DEV_NO_OWNER) {
		g_dpdkPortId++;
    }
    printf("ports:%d \n",g_dpdkPortId);
    if (g_dpdkPortId == RTE_MAX_ETHPORTS) {
        rte_exit(EXIT_FAILURE, "There were no DPDK ports free.\n");
    }
 
    const int num_rx_queues = 1;
    const int num_tx_queues = 0;
    struct rte_eth_conf port_conf = port_conf_default;
    if (rte_eth_dev_configure(g_dpdkPortId, num_rx_queues, num_tx_queues, &port_conf)) {
        rte_exit(EXIT_FAILURE, "rte_eth_dev_configure() failed.\n");
    }

    // 设置接收队列
    if (rte_eth_rx_queue_setup(g_dpdkPortId, DPDK_QUEUE_ID_RX, RX_RING_SIZE,
            rte_eth_dev_socket_id(g_dpdkPortId), NULL, mbuf_pool) < 0) {
        rte_exit(EXIT_FAILURE, "Couldn't setup RX queue.\n");
    }
    // 启动网卡
    if (rte_eth_dev_start(g_dpdkPortId) < 0) {
        rte_exit(EXIT_FAILURE, "Device start failed.\n");
    }

    // 设置为混杂模式
    rte_eth_promiscuous_enable(g_dpdkPortId);
}

当NIC处于混杂模式时,它将接收所有经过它的网络数据包,这样捕获所有的流量。

简单解析数据包

void process_packet(struct rte_mbuf *mbuf) {
    struct rte_ether_hdr *eth_hdr;
    struct rte_ipv4_hdr *ipv4_hdr;
    struct rte_tcp_hdr *tcp_hdr;
    struct rte_udp_hdr *udp_hdr;
    uint8_t *packet_data;
    uint16_t ether_type;
    uint16_t l4_len;

    // 获取数据包指针
    packet_data = rte_pktmbuf_mtod(mbuf, uint8_t *);
    eth_hdr = (struct rte_ether_hdr *)packet_data;

    // 输出以太网头部信息
    printf("MAC Src: ");
    for (int j = 0; j < RTE_ETHER_ADDR_LEN; j++) {
        printf("%02x", eth_hdr->s_addr.addr_bytes[j]);
        if (j < RTE_ETHER_ADDR_LEN - 1) printf(":");
    }
    printf(" -> MAC Dst: ");
    for (int j = 0; j < RTE_ETHER_ADDR_LEN; j++) {
        printf("%02x", eth_hdr->d_addr.addr_bytes[j]);
        if (j < RTE_ETHER_ADDR_LEN - 1) printf(":");
    }
    printf("\n");

    // 确保数据包是 IPv4 类型
    ether_type = rte_be_to_cpu_16(eth_hdr->ether_type);
    if (ether_type == RTE_ETHER_TYPE_IPV4) {
        // 操作IP数据包头部
        ipv4_hdr = (struct rte_ipv4_hdr *)(packet_data + sizeof(struct rte_ether_hdr));
        printf("IP Src: %d.%d.%d.%d -> IP Dst: %d.%d.%d.%d\n",
            ipv4_hdr->src_addr & 0xFF, (ipv4_hdr->src_addr >> 8) & 0xFF, (ipv4_hdr->src_addr >> 16) & 0xFF, (ipv4_hdr->src_addr >> 24) & 0xFF,
            ipv4_hdr->dst_addr & 0xFF, (ipv4_hdr->dst_addr >> 8) & 0xFF, (ipv4_hdr->dst_addr >> 16) & 0xFF, (ipv4_hdr->dst_addr >> 24) & 0xFF
        );

        // 计算传输层协议
        uint8_t proto = ipv4_hdr->next_proto_id;
        // 传输层协议长度
        l4_len = rte_be_to_cpu_16(ipv4_hdr->total_length) - (ipv4_hdr->version_ihl & RTE_IPV4_HDR_IHL_MASK) * 4;
        if (proto == IPPROTO_TCP) {
            // 计算TCP头部所在的位置
            tcp_hdr = (struct rte_tcp_hdr *)(packet_data + sizeof(struct rte_ether_hdr) + (ipv4_hdr->version_ihl & RTE_IPV4_HDR_IHL_MASK) * 4);
            printf("TCP Src Port: %d -> TCP Dst Port: %d\n",
                rte_be_to_cpu_16(tcp_hdr->src_port), rte_be_to_cpu_16(tcp_hdr->dst_port)
            );
        } else if (proto == IPPROTO_UDP) {
            udp_hdr = (struct rte_udp_hdr *)(packet_data + sizeof(struct rte_ether_hdr) + (ipv4_hdr->version_ihl & RTE_IPV4_HDR_IHL_MASK) * 4);
            printf("UDP Src Port: %d -> UDP Dst Port: %d\n",
                rte_be_to_cpu_16(udp_hdr->src_port), rte_be_to_cpu_16(udp_hdr->dst_port)
            );
        } else {
            printf("Unsupported IP protocol: %d\n", proto);
        }
    } else {
        printf("Unsupported Ethernet type: %x\n", ether_type);
    }
}

获取数据包内容rte_pktmbuf_mtod函数将数据包转换为uint8_t *类型,uint8_t表示单个字节,通过这个指针,可以逐字节地读取或写入数据包的任意部分,从而直接处理数据包的内容。

解析以太网头部:提取并打印源和目的MAC地址。RTE_ETHER_ADDR_LEN用于确定MAC地址的长度。

检查以太网类型:通过rte_be_to_cpu_16将网络字节顺序的以太网类型转换为主机字节顺序。支持IPv4类型的数据包。

解析IPv4头部:提取并打印源和目的IP地址。计算IP头部长度和总长度,以确定传输层数据的起始位置。

处理传输层协议:根据IPv4头部中的next_proto_id字段,判断是TCP还是UDP协议,并解析相应的头部信息。对于TCP和UDP数据包,打印源和目的端口号。

输出不支持的协议:如果以太网类型或IP协议不是支持的类型,打印相应的错误信息。

主函数

int main(int argc, char *argv[]) {
    // 初始化了 DPDK 的环境抽象层
    if (rte_eal_init(argc, argv) < 0) {
        rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");
    }

    struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS,
        MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
    if (!mbuf_pool) {
        rte_exit(EXIT_FAILURE, "Couldn't create mbuf pool\n");
    }

    port_init(mbuf_pool);
    static uint8_t g_mac_addr[ETH_ALEN];
    // 获取此时的mac地址输出
    rte_eth_macaddr_get(g_dpdkPortId, (struct ether_addr *)g_mac_addr);
    printf("Our MAC: %02x %02x %02x %02x %02x %02x\n",
        g_mac_addr[0], g_mac_addr[1],
        g_mac_addr[2], g_mac_addr[3],
        g_mac_addr[4], g_mac_addr[5]);
    
    while (1) {
        struct rte_mbuf *mbufs[BURST_SIZE];
        unsigned num_recvd = rte_eth_rx_burst(g_dpdkPortId, DPDK_QUEUE_ID_RX, mbufs, BURST_SIZE);
        for (unsigned i = 0; i < num_recvd; i++) {
            process_packet(mbufs[i]);
            rte_pktmbuf_free(mbufs[i]);
        }
    }
    return 0;
}

DPDK环境初始化:使用rte_eal_init初始化DPDK环境抽象层(EAL)。

创建内存池:用于存储数据包缓冲区(mbuf)

端口初始化:调用port_init函数来初始化网络端口,配置其使用创建的内存池。

主循环:调用rte_eth_rx_burst从网络端口接收数据包,并将接收到的数据包传递给process_packet进行处理。处理完成后,释放mbuf。

编译

cmake_minimum_required(VERSION 3.10)
project(dpdk_receive)

# 设置 C 标准
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)

# 设定编译选项
add_compile_options(-O3 -march=native)
add_definitions(-DALLOW_EXPERIMENTAL_API)

# 查找 DPDK 包
find_package(PkgConfig REQUIRED)
pkg_check_modules(RTE REQUIRED libdpdk)

# 包含 DPDK 头文件
include_directories(${RTE_INCLUDE_DIRS})
add_executable(recv recv.c)

# 链接 DPDK 库
target_link_libraries(recv ${RTE_LIBRARIES})

# 设置库搜索路径
link_directories(/usr/local/lib/x86_64-linux-gnu)

# 链接选项
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")

在这里插入图片描述

完整代码可见 github

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2148017.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024最新版 Tuxera NTFS for Mac 2023绿色版图文安装教程

​ 在数字化时代&#xff0c;数据的存储和传输变得至关重要。Mac用户经常需要在Windows NTFS格式的移动硬盘上进行读写操作&#xff0c;然而&#xff0c;由于MacOS系统默认不支持NTFS的写操作&#xff0c;这就需要我们寻找一款高效的读写软件。Tuxera NTFS for Mac 2023便是其中…

超详图解 Apache HTTP Server(httpd)安装与验证

在OpenEuler 24.03系统中安装验证 Apache HTTP Server&#xff08;httpd&#xff09;的过程通常涉及以下步骤&#xff1a; 一、Apache HTTP Server&#xff08;httpd&#xff09;安装 1.检查是否已安装httpd: rpm -q httpd 2.更新系统包索引&#xff1a;更新您的系统包索引以…

基于深度学习的药品三期OCR字符识别

在药品生产线上,药品三期的喷码与条形码识别是保证药品追溯和安全管理的重要环节。传统的识别方法依赖于人工操作,不仅效率低下且容易出错。随着深度学习技术的不断发展,基于OCR(Optical Character Recognition,光学字符识别)的自动化识别系统逐渐成为主流。本文将以哪吒…

【Godot4.2】基于EasyTreeData解析的扩展Tree控件 - ETDTree

概述 基于EasyTreeData解析的扩展Tree控件。 EasyTreeData&#xff08;ETD&#xff09; EasyTreeData&#xff08;ETD&#xff09;是一种基于Tab缩进的简单层级结构数据&#xff0c;可以用于描述树形结构。能够被解析为Tree控件或表示树形结构的其他类或控件。 根目录 | 0节…

cadence SPB17.4 - allegro - 用板子外形创建整板铺铜

文章目录 cadence SPB17.4 - allegro - 用板子外形创建整板铺铜概述笔记先确定自己板子的 board Geometry/Design_Outline 是否有外形shape为了将软件提示看得更清楚&#xff0c;在每个操作之前&#xff0c;先将命令提示区内容先删了用Z-copy从外形层生成整板的铺铜备注END cad…

Maven 和 gradle JavaFX 项目的休眠行为差异

我一直在尝试将Hibernate与我的JavaFX Maven项目集成。它与Hibernate社区包、Jakarta和xerial配合得很好。我还将persistence.xml文件放在了src/main/resources/META-INF/persistence.xml。 我还尝试使用gradle创建另一个项目&#xff0c;并按照此maven项目的步骤操作&#xf…

《高等代数》行列式转置(应用)

说明&#xff1a;此文章用于本人复习巩固&#xff0c;如果也能帮助到大家那就更加有意义了。 注&#xff1a;1&#xff09;“行列式转置值不变”这一性质在求解行列式的过程中也有极大的作用。

代码随想录算法训练营第51天 | 岛屿数量、岛屿的最大面积

目录 岛屿数量 题目描述 输入描述 输出描述 输入示例 输出示例 提示信息 1. 深搜解法 2. 广搜解法 岛屿的最大面积 题目描述 输入描述 输出描述 输入示例 输出示例 提示信息 1. 深搜解法 2. 广搜解法 岛屿数量 题目描述 给定一个由 1&#xff08;陆地&…

Python和C++气候模型算法模型气候学模拟和统计学数据可视化及指标评估

&#x1f3af;要点 贝叶斯推理气候模型辐射对流及干湿能量平衡模型时间空间气象变化预测模型评估统计指标气象预测数据变换天气和气象变化长短期影响预估降低气候信息尺度评估算法气象行为模拟&#xff1a;碳循环、辐射强迫和温度响应温室气体排放碳循环温室诱导气候变化评估气…

影刀RPA实战:网页爬虫之苦瓜书籍数据

书籍常常被视为心灵的慰藉&#xff0c;因为它们能够在不同的层面上为人们提供支持和安慰。 1. 书籍对我们的重要性 书籍是人类知识的载体&#xff0c;也是智慧的结晶。它们不仅是学习的工具&#xff0c;更是人类心灵的慰藉。在忙碌的生活中&#xff0c;书籍能够提供知识、启发…

VMWare17.5.2中Windows7企业版安装VMWareTools失败及解决办法

一、问题产生环境 宿主机系统&#xff1a;Windows11专业版 x64 虚拟机版本&#xff1a;VMWare17.5.2 虚拟机系统&#xff1a;Windows 7企业版 x64&#xff08;sp1纯净版&#xff09; 二、问题表现现象 在Windows 7企业版系统安装完成后&#xff0c;点击虚拟机编辑&#xff0c;…

安科瑞智能塑壳断路器适用于物联网配电电网中-安科瑞黄安南

智能塑壳断路器是一款多功能、体积小、智能化的塑壳断路器&#xff0c;适用于物联网配电电网中。用于三相四线中性点直接接地的供电、用电系统&#xff0c;能全面采集功率、电能量、功率因数、谐波等用电参数;具有过载、短路、缺相、过压、欠压、剩余电流动作保护等功能&#x…

vscode 环境搭建

1. 插件离线安装 官网链接是&#xff1a;https://marketplace.visualstudio.com/vscode 下载需要的插件&#xff1a; vscode 离线安装 在打开的文件中选择扩展包&#xff0c;点击安装即可 2. 故障解决 参考&#xff1a;https://blog.csdn.net/weixin_63712639/article/det…

Modbus_RTU和Modbus库

目录 一.Modbus_RTU 1. 与Modbus TCP的区别 2. Modbus RTU特点 3. Modbus RTU协议格式 4. 报文详解 5. 代码实现RTU通信 1. 打开模拟的RTU从机 2. linux端使用代码实现和串口连接 2.1. 框架搭建 2.2 代码 二.Modbus库 1.库函数 一.Modbus_RTU 1. 与Modbus T…

el-form中三级动态添加数据

el-form中三级动态添加数据 data数据view按钮触发事件 data数据 submitForm: {id: undefined, //修改IDapp_id: undefined, //IP类型name: , //规则名称sort: undefined, //排序detail: [{keycode: 0,title_one: undefined, //一级标题desc_detail: [{keycode: 0,title_two: u…

vue之我不会 计算属性 vuex 路由 插槽

一、计算属性 例子&#xff1a; 注意&#xff1a;调用计算属性时&#xff0c;不可以带括号&#xff0c;那样调用的就是方法&#xff0c;如&#xff1a;以下调用fullName时不可funnName() <div id"root">姓&#xff1a;<input type"text" v-model&…

iotop 命令:磁盘IO监控和诊断

一、命令简介 ​iotop​命令用于监视磁盘I/O&#xff0c;实时显示每个进程或线程的读写速率等信息。非常适合用于诊断系统中的I/O瓶颈。 ‍ ​​ ‍ 安装 iotop 在大多数Linux发行版中&#xff0c;iotop​可能不是预装的。可以使用包管理器来安装它。 例如&#xff0c;在…

C#|.net core 基础 - 扩展数组添加删除性能最好的方法

今天在编码的时候遇到了一个问题&#xff0c;需要对数组变量添加新元素和删除元素&#xff0c;因为数组是固定大小的&#xff0c;因此对新增和删除并不友好&#xff0c;但有时候又会用到&#xff0c;因此想针对数组封装两个扩展方法&#xff1a;新增元素与删除元素&#xff0c;…

MySQL5.7.42高可用MHA搭建及故障切换演示

系列文章目录 rpmbuild构建mysql5.7RPM安装包 MySQL基于GTID同步模式搭建主从复制 文章目录 系列文章目录前言一、MHA架构介绍1.MHA的功能2.MHA组成3.MHA故障转移过程4.MHA架构优缺点 二、环境准备1.服务器免密2.基于GTID主从复制搭建3.下载mha组件 三、MHA组件安装1.安装依赖…

数据结构——C语言单链表的实现

单链表的实现 一.链表的节点二.如何在在链表中插入数据1.尾插2.改进3.头插4.指定位置pos&#xff0c;在pos前插入数据 三 .删除数据1.头删2.尾删3.指定位置删除数据 一.链表的节点 //链表中的数据类型,方便后续的更改 typedef int SLTDatatype;//链表的节点 typedef struct SL…