【DPDK】基于dpdk实现用户态UDP网络协议栈

文章目录

  • 一.背景及导言
  • 二.协议栈架构设计
    • 1. 数据包接收和发送引擎
    • 2. 协议解析
    • 3. 数据包处理逻辑
  • 三.网络函数编写
    • 1.socket
    • 2.bind
    • 3.recvfrom
    • 4.sendto
    • 5.close
  • 四.总结

一.背景及导言

在当今数字化的世界中,网络通信的高性能和低延迟对于许多应用至关重要。而用户态网络协议栈通过摆脱传统内核态协议栈的限制,为实现更快速、灵活的数据包处理提供了新的可能性。本文将深入探讨基于DPDK的用户态UDP网络协议栈的设计、实现。

传统的内核态协议栈在处理网络通信时通常伴随着较大的性能开销,而用户态网络协议栈的崛起为高性能应用带来了全新的解决方案。DPDK,作为一款用于高性能数据平面应用的工具包,为用户态网络协议栈的实现提供了强大的支持。通过将网络协议栈移植到用户态,我们可以更灵活地优化数据包处理、提高吞吐量,并有效降低处理延迟。

二.协议栈架构设计

网络协议栈整体大致架构如下图所示:
在这里插入图片描述

1. 数据包接收和发送引擎

数据包接收和发送引擎负责从网络接口接收数据包,并将数据包发送到目标地址。通过DPDK提供的高性能数据包I/O接口,实现对多队列的支持,以提高并行性和吞吐量。

从网卡接收原始数据放入in_ring:
rte_eth_rx_burst();
out_ring中取出数据通过网卡发送:
rte_eth_tx_burst();

while(1) {

        // rx
        struct rte_mbuf *rx[BURST_SIZE];// 内存池
        //接收
        unsigned num_recvd = rte_eth_rx_burst(gDpdkPortId, 0, rx, BURST_SIZE);
        if(num_recvd > BURST_SIZE) {
            rte_exit(EXIT_FAILURE, "Error receiving from eth\n");
        } else if(num_recvd > 0) {
            //入队列
            rte_ring_sp_enqueue_burst(ring->in, (void**)rx, num_recvd, NULL);
        }

        // tx
        struct rte_mbuf *tx[BURST_SIZE];
        //出队列
        unsigned nb_tx = rte_ring_sc_dequeue_burst(ring->out, (void**)tx, BURST_SIZE,NULL);
        if(nb_tx > 0) {
            //发送
            rte_eth_tx_burst(gDpdkPortId, 0, tx, nb_tx);
            unsigned i = 0;
            for(;i < nb_tx; i++) {
                rte_pktmbuf_free(tx[i]);
            }
        }
        
        static uint64_t prev_tsc = 0, cur_tsc;
        uint64_t diff_tsc;

        cur_tsc = rte_rdtsc();
        diff_tsc = cur_tsc - prev_tsc;
        if(diff_tsc > TIMER_RESOLUTION_CYCLES) {
            rte_timer_manage();
            prev_tsc = cur_tsc;
        }
    }

2. 协议解析

协议解析模块负责对接收到的UDP数据包进行解析,提取出源和目标端口号、校验和等关键信息。采用高效的解析算法,确保对数据包的处理不成为性能瓶颈。
从原始数据包中解析以太网头:

struct rte_ether_hdr *ehdr = rte_pktmbuf_mtod(mbufs[i],struct rte_ether_hdr*);

从原始数据包中(偏移以太网头)解析arp头:

struct rte_arp_hdr *ahdr = rte_pktmbuf_mtod_offset(mbufs[i],struct rte_arp_hdr *,
                     sizeof(struct rte_ether_hdr));

从原始数据包中解析IP头:

struct rte_ipv4_hdr *iphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr *, 
                sizeof(struct rte_ether_hdr));

通过IP头中的网络类型协议可以得知该数据包是UDP,TCP或ICMP包,通过类型强制转换可以得到相对应的数据包协议头。
通过IP头偏移1位强转可得到UDP/TCP头:

struct rte_udp_hdr *udphdr = (struct rte_udp_hdr *)(iphdr + 1);

通过IP头偏移1位强转可得到ICMP头:

struct rte_icmp_hdr *icmphdr = (struct rte_icmp_hdr *)(iphdr + 1);

不同的数据包调用不同的函数处理,通过对数据包的解析可以得到我们想要的IP地址,端口号,以太网地址,数据等。

3. 数据包处理逻辑

数据包处理逻辑包括各种应用层的逻辑,如数据包过滤、路由决策等。这一部分需要具体根据应用场景进行定制,以满足不同需求。
当用户接收并处理完数据包后得到新的用户数据需要发送,此时我们只需要逆向操作接收数据包的过程即可。
一个UDP数据帧组成结构如图所示,在用户数据上添加UDP头,在此基础上再添加IP头,最后再添加以太网头,一个UDP数据帧就组装完毕,就可直接通过网卡发送。
在这里插入图片描述
按UDP数据帧结构从用户数据从上往下依次组包。
在这里插入图片描述
!](https://img-blog.csdnimg.cn/direct/ede89757233f4dca8eff2eec63826075.png)

//1 ether
    struct rte_ether_hdr *eth = (struct rte_ether_hdr*)msg;
    rte_memcpy(eth->s_addr.addr_bytes, src_mac, RTE_ETHER_ADDR_LEN);//源Mac地址
    rte_memcpy(eth->d_addr.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN);//目的Mac地址
    eth->ether_type = htons(RTE_ETHER_TYPE_IPV4);//类型

在这里插入图片描述

//2 iphdr
    struct rte_ipv4_hdr *ip = (struct rte_ipv4_hdr*)(msg + sizeof(struct rte_ether_hdr));
    ip->version_ihl = 0x45; //4位版本,4位首部长度
    ip->type_of_service = 0;//服务类型
    ip->total_length = htons(length - sizeof(struct rte_ether_hdr));//总长度
    ip->packet_id = 0;//16位标识
    ip->fragment_offset = 0;//偏移
    ip->time_to_live = 64; //TTL
    ip->next_proto_id = IPPROTO_UDP;//8位协议
    ip->src_addr = sip;
    ip->dst_addr = dip;

    ip->hdr_checksum = 0;
    ip->hdr_checksum = rte_ipv4_cksum(ip);//首部校验和

UDP协议

//3 udp
    struct rte_udp_hdr *udp = (struct rte_udp_hdr*)(msg + sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr));
    udp->src_port = sport;//源端口
    udp->dst_port = dport;//目的端口
    uint16_t udplen = length - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr);
    udp->dgram_len = htons(udplen);//长度

    rte_memcpy((uint8_t*)(udp + 1), data, udplen);

    udp->dgram_cksum = 0;
    udp->dgram_cksum = rte_ipv4_udptcp_cksum(ip, udp);//校验和

所有数据包都有以太网头,IP头arp头为第二层,TCP UDP ICMP为第三次,数据组包的时候只需根据需求选择不同的协议填空即可。

三.网络函数编写

定义主机,包括:唯一标识符,IP地址,Mac地址,协议,recvbuf,senfbuf,互斥锁,条件变量,链表结构。

struct localhost {
    int fd;
    uint32_t localip;
    uint8_t localmac[RTE_ETHER_ADDR_LEN];
    uint16_t localport;

    uint8_t protocol;

    struct rte_ring *recvbuf;
    struct rte_ring *sendbuf;

    struct localhost *prev;
    struct localhost *next;

    pthread_cond_t cond;
    pthread_mutex_t mutex;
};

static struct localhost *lhost = NULL;

使用Hook自定义网络编程函数,或自定义网络函数名。

1.socket

static int 
socket(__attribute__((unused))int domain, int type, __attribute__((unused))int protocol) {
    int fd = get_fd_frombitmap();

    struct localhost *host = rte_malloc("localhost", sizeof(struct localhost), 0);
    if(host == NULL) {
        
        return -1;
    }
    memset(host, 0, sizeof(struct localhost));
    host->fd = fd;

    if(type == SOCK_DGRAM) {
        host->protocol = IPPROTO_UDP;
    } 

    host->recvbuf =  rte_ring_create("recv buf",RING_SIZE,rte_socket_id(),RING_F_SP_ENQ | RING_F_SC_DEQ);
    if(host->recvbuf == NULL) {
        rte_free(host);
        
        return -1;
    }
    host->sendbuf =  rte_ring_create("send buf",RING_SIZE,rte_socket_id(),RING_F_SP_ENQ | RING_F_SC_DEQ);
    if(host->sendbuf == NULL) {
        rte_ring_free(host->recvbuf);
        rte_free(host);
        
        return -1;
    }

    pthread_cond_t blank_cond = PTHREAD_COND_INITIALIZER;
    rte_memcpy(&host->cond, &blank_cond, sizeof(pthread_cond_t));

    pthread_mutex_t blank_mutex = PTHREAD_MUTEX_INITIALIZER;
    rte_memcpy(&host->mutex, &blank_mutex, sizeof(pthread_mutex_t));

    LL_ADD(host, lhost);
 
    return fd;
}

2.bind

static int bind(int sockfd, const struct sockaddr *addr,__attribute__((unused))socklen_t addrlen) {

    struct localhost *host = get_hostinfo_fromfd(sockfd);
    if(host == NULL) {
        return -1;
    }
    const struct sockaddr_in *laddr = (const struct sockaddr_in*)addr;
    host->localport = laddr->sin_port;
    rte_memcpy(&host->localip, &laddr->sin_addr.s_addr, sizeof(uint32_t));
    rte_memcpy(host->localmac, gSrcMac, RTE_ETHER_ADDR_LEN);

    return 0;
}

3.recvfrom

static ssize_t recvfrom(int sockfd, void *buf, size_t len, __attribute__((unused))int flags,
                        struct sockaddr *src_addr, __attribute__((unused))socklen_t *addrlen){

    struct localhost *host = get_hostinfo_fromfd(sockfd);
    
    if(host == NULL) return -1;
    
    struct sockaddr_in *saddr = (struct sockaddr_in*)src_addr;
    
    //dequeue
    struct offload *ol = NULL;

    unsigned char *ptr = NULL;
    
    int nb = -1;
    //阻塞
    pthread_mutex_lock(&host->mutex);
    while((nb = rte_ring_mc_dequeue(host->recvbuf,(void**)&ol)) < 0) {
        pthread_cond_wait(&host->cond, &host->mutex);
    }
    pthread_mutex_unlock(&host->mutex);

    saddr->sin_port = ol->sport;
    rte_memcpy(&saddr->sin_addr.s_addr, &ol->sip, sizeof(uint32_t));

    struct in_addr addr;
	addr.s_addr = ol->dip;
    printf("nrecvto ---> src: %s:%d \n", inet_ntoa(addr), ntohs(ol->dport));

    if(len < ol->length) { //一次无法接收全部数据
        rte_memcpy(buf, ol->data, len);
        ptr = rte_malloc("unsigned char *", ol->length - len, 0);
        rte_memcpy(ptr, ol->data + len, ol->length - len);
        ol->length -= len;
        rte_free(ol->data);
        ol->data = ptr;

        rte_ring_mp_enqueue(host->recvbuf, ol);
        return len;
    } else {
        rte_memcpy(buf, ol->data, ol->length);
        rte_free(ol->data);
        rte_free(ol);
        return ol->length;
    }
}

4.sendto

static ssize_t sendto(int sockfd, const void *buf, size_t len, __attribute__((unused))int flags,
                      const struct sockaddr *dest_addr, __attribute__((unused))socklen_t addrlen){
    struct localhost *host = get_hostinfo_fromfd(sockfd);
    if(host == NULL) return -1;
    const struct sockaddr_in *daddr = (const struct sockaddr_in*)dest_addr;
    struct offload *ol = rte_malloc("offload", sizeof(struct offload), 0);
    if(ol == NULL) {
        return -1;
    }
    ol->dip = daddr->sin_addr.s_addr;
    ol->dport = daddr->sin_port;
    ol->sip = host->localip;
    ol->sport = host->localport;
    ol->length = len;

	struct in_addr addr;
	addr.s_addr = ol->dip;
	printf("nsendto ---> src: %s:%d \n", inet_ntoa(addr), ntohs(ol->dport));

    ol->data = rte_malloc("ol data", len, 0);
    if(ol->data == NULL) {
        rte_free(ol);
        return -1;
    }
    rte_memcpy(ol->data, buf, len);

    rte_ring_mp_enqueue(host->sendbuf, ol);
    
    return len;   
}

5.close

static int nclose(int fd) {
    struct localhost *host = get_hostinfo_fromfd(fd);
    if(host == NULL) {
        return -1;
    }
    LL_REMOVE(host, lhost);
    if(host->recvbuf){
        rte_ring_free(host->recvbuf);
    }
    if(host->sendbuf){
        rte_ring_free(host->sendbuf);
    }
    
    rte_free(host);

    return 0;
}

四.总结

通过本文,我们深入研究了基于DPDK的用户态UDP网络协议栈的设计、实现。在整体设计思路上,我们采用了用户态网络协议栈的理念,通过将核心功能移至用户空间,结合DPDK的强大支持,实现了一个高性能、低延迟的数据包处理方案。

关键组成部分中,我们详细介绍了数据包接收和发送引擎、协议解析、数据包处理逻辑等模块。这些组成部分共同协作,使得用户态UDP网络协议栈能够在不同应用场景下发挥其优势。

整体架构图清晰展示了各个模块之间的关系,以及数据在协议栈中的流动路径。这有助于读者更好地理解我们设计的用户态UDP网络协议栈的整体结构。

通过对用户态UDP网络协议栈的研究,我们不仅深刻理解了其设计和实现,也为构建更高性能、更灵活的网络通信系统奠定了基础。未来,我们期待在这一基础上进一步优化和扩展,以满足不断发展的网络应用需求。

链接: 基于DPDK实现的UDP用户态网络协议栈完整代码

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

并发通信(网络进程线程)

如果为每个客户端创建一个进程&#xff08;或线程&#xff09;&#xff0c;因为linux系统文件标识符最多1024位&#xff0c;是有限的。 所以使用IO复用技术&#xff0c;提高并发程度。 阻塞与非阻塞 阻塞式复用 非阻塞复用 信号驱动IO 在属主进程&#xff08;线程中声明&…

4、Generator、class类、继承、Set、Map、Promise

一、生成器函数Generator 1、声明generator函数 function* 函数名() { }调用生成器函数 需要next()返回一个generator对象&#xff0c;对象的原型上有一个next(),调用返回对象{value:yield后面的值,done} function* fn() {console.log("我是生成器函数") } let it…

JAVA开发常见小问题整合

文章目录 1&#xff1a;身份证工具类相关方法1.1 身份证脱敏处理 2&#xff1a;字符串补零处理(此处是JAVA类的方法&#xff0c;并无引用StrUtil)3&#xff1a;springboot前后端分离&#xff0c;后端返回json字符串带斜杠问题处理4&#xff1a;WebUploader 文件上传组件 -编辑回…

java基本认识?java跨平台原理?jdk、jre、jvm的联系?

1、java基本认识 1.1 java语言 语言&#xff1a;人与人交流沟通的方式。比如&#xff0c;你好、hello等。 计算机语言&#xff1a;人与计算机之间进行信息交流的一种特殊方式。比如&#xff0c;Java语言、C语言、C等。 1.2 java的来源 Java 是由 Sun Microsystems 公司于 …

如何正确选择国外服务器的带宽和线路呢?

国外大带宽服务器是一种提供高带宽、高速网络连接和良好稳定性的服务器&#xff0c;但在中国使用这类服务器可能涉及到违反法律法规的风险。因此我无法为你提供相关帮助。接下来和源库一起了解如何正确选择国外服务器的带宽和线路呢? 考虑目标用户的地理位置。如果目标用户主要…

计算机网络-第5章 运输层(1)

主要内容&#xff1a;进程之间的通信与端口、UDP协议、TCP协议、可靠传输原理&#xff08;停止等待协议、ARQ协议&#xff09;、TCP报文首部、TCP三大题&#xff1a;滑动窗口、流量控制、拥塞控制机制 5.1 运输层协议概述 运输层向它上面的应用层提供通信服务&#xff0c;真正…

window Zookeeper 启动;

文章目录 前言一、Zookeeper 介绍&#xff1a;二、window 使用&#xff1a;2.1 下载&#xff1a;2.2 启动2.3 连接&#xff1a; 总结 前言 本文对window Zookeeper zk 启动 进行介绍&#xff1b; 一、Zookeeper 介绍&#xff1a; ZooKeeper 是一个开源的分布式协调服务&#…

辽宁博学优晨教育:视频剪辑培训的安全正规之路

在当今数字化时代&#xff0c;视频剪辑已成为一项炙手可热的技能。为满足广大学习者的需求&#xff0c;辽宁博学优晨教育推出了一系列专业的视频剪辑培训课程。本文将重点介绍辽宁博学优晨教育的视频剪辑培训如何在保障学员安全和学习效果方面做出了卓越的努力。 一、正规资质&…

PID控制器组(完整SCL代码)

PID控制器组不是什么新概念,是在PID控制器的基础上,利用面向对象的思想对对象进行封装 批量实例化。 1、增量式PID https://rxxw-control.blog.csdn.net/article/details/124363197https://rxxw-control.blog.csdn.net/article/details/1243631972、完全增量式PID https:/…

5款好用的AI办公软件,一键轻松制作PPT、视频,提升工作效率!

众所周知&#xff0c;AI 人工智能技术已渗透到生活的方方面面&#xff0c;无论是很多人早已用上的智能音箱、语音助手&#xff0c;还是新近诞生的各种 AI 软件工具&#xff0c;背后都离不开 AI 人工智能技术的加持。 对于各类新生的 AI 软件工具&#xff0c;人们很容易「选边站…

free pascal 调用 C#程序读 Freeplane.mm文件,生成测试用例.csv文件

C# 请参阅&#xff1a;C# 用 System.Xml 读 Freeplane.mm文件&#xff0c;生成测试用例.csv文件 Freeplane 是一款基于 Java 的开源软件&#xff0c;继承 Freemind 的思维导图工具软件&#xff0c;它扩展了知识管理功能&#xff0c;在 Freemind 上增加了一些额外的功能&#x…

Java基于SpringBoot+Vue的人事管理系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

QML | 在QML中导入JavaScript资源、导入JavaScript资源、包含一个JavaScript 资源

01 在QML中导入JavaScript资源 JavaScript资源可以被QML文档和其他JavaScript通过相对或者绝对路径进行导入。如果使用相对路径,位置解析需要相对于包含import语句的QML文档或JavaScript资源的位置。如果JavaScript需要从网络资源中进行获取,组件的status属性会被设置为Loadi…

MinGW-w64的下载与安装

文章目录 1 下载2 安装3 配置环境变量4 验证 1 下载 官网地址&#xff1a;https://www.mingw-w64.org/github地址&#xff1a;https://github.com/niXman/mingw-builds-binaries/releases windows下载 跳转github下载 版本号选择&#xff1a;13.2.0是GCC的版本号&#xff1b…

SpringBoot中定时任务、corn表达式

SpringBoot中定时任务、corn表达式 corn表达式网站&#xff1a;https://cron.qqe2.com/ 方法上加上Scheduled(cron表达式) 启动类上加上EnableScheduling 示例 启动类上 启动类加上EnableScheduling开启定时任务。 SpringBootApplication EnableScheduling public class…

简单句,并列句【语法笔记】

1. 简单句&#xff0c;并列句本质分别是什么 2. 如何区分简单句和并列句 3. 连接词 4. 简单句的五大基本句型 5. 有连接词&#xff0c;未必都是并列句&#xff0c;这是为什么

C语言基础练习——Day05

目录 选择题 编程题 数字在升序数组中出现的次数 整数转换 选择题 1、如下程序的功能是 #include <stdio.h> int main() {char ch[80] "123abcdEFG*&";int j;puts(ch);for(j 0; ch[j] ! \0; j){if(ch[j] > A && ch[j] < Z)ch[j] ch[j] e…

《计算机网络》考研:2024/3/7 2.1.4 奈氏准则和香农定理

2024/3/7 (作者转行去干LLMs了&#xff0c;但是又想搞定考研&#xff0c;忙不过来了就全截图了呜呜呜。。。 生活真不容易。) 2.1.4 奈氏准则与香农定理

SpringCloudAlibaba 网关gateway整合sentinel日志默认路径修改

SpringCloudAlibaba 网关gateway整合sentinel 实现网关限流熔断 问题提出 今天运维突然告诉我 在服务器上内存满了 原因是nacos日志高达3G,然后将日志文件发给我看了一下之后才发现是gateway整合sentinel使用了默认日志地址导致日志生成地址直接存在与根路径下而且一下存在多…

基于SpringBoot+Vue+ElementUI+Mybatis前后端分离管理系统超详细教程(五)——多条件搜索并分页展示

前后端数据交互 书接上文&#xff0c;我们上节课通过前后端数据交互实现了分页查询和单条件搜索分页查询的功能&#xff0c;最后留了个小尾巴&#xff0c;就是把其他两个搜索条件&#xff08;email,address&#xff09;也加进来&#xff0c;实现多条件搜索并分页展示。这节课我…