DPDK 简易应用开发之路 4:基于Pipeline模型的DNS服务器

本机环境为 Ubuntu20.04 ,dpdk-stable-20.11.10
使用scapy和wireshark发包抓包分析结果
完整代码见:github

Pipeline模型

DPDK Pipeline模型是基于Data Plane Development Kit(DPDK)的高性能数据包处理框架。它通过将数据流分为多个处理阶段,支持高效的数据包转发和处理。其架构包括多个模块,如数据接收、处理、转发和发送,每个模块可以独立优化,以提高性能和灵活性。

对DPDK-DNS收发包阶段进行时间上的阶段划分,使得每个阶段的处理时间尽可能均衡。每个处理阶段绑定一个逻辑核(lcore)进行处理。

DNS 协议

DNS资源记录

在DNS(域名系统)协议中,DNS资源记录(DNS Resource Record,RR)是DNS系统中存储数据的基本单位,每条记录存储与域名相关的信息。这些记录主要由主机名、IP地址、服务信息等构成,能够帮助解析器把域名转换为IP地址或其他信息。

每个DNS资源记录包含以下字段:

  • 名称(Name):表示与该记录关联的域名,通常是需要查询的域名。
  • 类型(Type):指定该记录的类型,表示该记录提供的信息类别,例如A记录、MX记录等。
  • 类(Class):指定该记录所属的协议族。常见的是IN,代表互联网。
  • 生存时间(TTL, Time to Live):记录在DNS服务器中的缓存时间,以秒为单位。TTL决定了缓存中这条记录在多长时间后失效。
  • 数据长度(RDLENGTH):指记录中数据的长度,以字节为单位。
  • 资源数据(RDATA):存储实际的数据,内容取决于记录的类型,比如A记录中的IP地址、MX记录中的邮件服务器信息等。

数据结构定义如下:

struct ResourceRecord {char *name;          // 资源记录的名称(域名)。uint16_t type;       //  资源记录的类型(例如 A、NS、MX、AAAA 等)。uint16_t rr_class;      // 资源记录的类,通常为 IN 类(1 表示互联网类)。uint16_t ttl;        // TTL(生存时间),表示该记录在缓存中保留的时间(以秒为单位)。uint16_t rd_length;  // 资源数据的长度,以字节为单位。union ResourceData rd_data;  // 资源记录的数据部分,使用上面定义的 ResourceData 联合体表示。struct ResourceRecord* next; // 指向下一个资源记录的指针,用于将多个资源记录组织成链表。
};

整体及Header部分

DNS请求与响应的格式是一致的,其整体分为Header、Question、Answer、Authority、Additional5部分,如下图所示:

在这里插入图片描述
Header部分是一定有的,长度固定为12个字节;其余4部分可能有也可能没有,并且长度也不一定,这个在Header部分中有指明。Header的结构如下:

在这里插入图片描述

其中Header部分处理的数据结构如下:

struct Message {uint16_t id;          // 标识符,用于匹配请求和响应。请求和响应的 ID 应相同。/* 标志位 */uint16_t qr;          // 查询/响应标志,0 表示查询,1 表示响应。uint16_t opcode;      // 操作码,表示查询类型,0 表示标准查询,1 表示反向查询。uint16_t aa;          // 授权回答标志,1 表示响应是来自授权域名服务器。uint16_t tc;          // 截断标志,1 表示响应被截断(超出 UDP 数据包大小)。uint16_t rd;          // 期望递归标志,1 表示客户端希望服务器执行递归查询。uint16_t ra;          // 可用递归标志,1 表示服务器支持递归查询。uint16_t rcode;       // 响应码,表示查询的状态,如 0 表示无错误,3 表示域名不存在。uint16_t qdCount;     // 问题记录数,表示查询中的问题数量。uint16_t anCount;     // 回答记录数,表示响应中的回答记录数量。uint16_t nsCount;     // 授权记录数,表示授权记录数量。uint16_t arCount;     // 附加记录数,表示附加记录数量。struct Question* questions;         // 指向问题记录的指针,可能有多个问题记录(链表)。struct ResourceRecord* answers;     // 指向回答记录的指针,可能有多个回答记录(链表)。struct ResourceRecord* authorities; // 指向授权记录的指针,可能有多个授权记录(链表)。struct ResourceRecord* additionals; // 指向附加记录的指针,可能有多个附加记录(链表)。
};

Question部分

Question部分的每一个实体的格式如下图所示:

在这里插入图片描述
Q主机名被"."号分割成了多段标签。在QNAME中,每段标签前面加一个数字,表示接下来标签的长度。比如:api.sina.com.cn表示成QNAME时,会在"api"前面加上一个字节0x03,"sina"前面加上一个字节0x04,"com"前面加上一个字节0x03,而"cn"前面加上一个字节0x02;

struct Question {char *qName;        // 问题的域名,例如 "www.example.com"。以字符指针形式存储,DNS 协议要求该名字使用特殊格式。uint16_t qType;     // 问题的类型。例如,1 代表 A 记录(IPv4 地址),28 代表 AAAA 记录(IPv6 地址)。uint16_t qClass;    // 问题的类。通常为 1,表示互联网类(IN)。struct Question* next;  // 指向下一个问题的指针,用于将多个问题组织成链表。因为 DNS 查询可以包含多个问题。
};

部分代码实现

代码较多,本文省略DNS具体实现步骤,着重看pipeline模型的实现。

主函数

初始化网卡、内存池、环形队列以及启动其他工作核心,该核心同时处理转发数据包的任务。在启动时为其他核心分配任务,设置接收(RX)和发送(TX)核心。

int main(int argc, char *argv[])
{uint8_t ip[4] = {192, 168, 1, 1};add_A_record("foo.bar.com",ip);unsigned lcore_id;uint16_t portid = 0, nb_ports = 1;int ret = rte_eal_init(argc, argv);if (ret < 0)rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");argc -= ret;argv += ret;force_quit = false;signal(SIGINT, signal_handler);signal(SIGTERM, signal_handler);mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS * nb_ports,MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());if (mbuf_pool == NULL)rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n");if (port_init(portid, mbuf_pool) != 0)rte_exit(EXIT_FAILURE, "Cannot init port %"PRIu16 "\n", portid);struct rte_ring *rx_ring = rte_ring_create("Input_ring", SCHED_RX_RING_SZ,rte_socket_id(), RING_F_SC_DEQ | RING_F_SP_ENQ);if (rx_ring == NULL)rte_exit(EXIT_FAILURE, "Cannot create output ring\n");struct rte_ring *tx_ring = rte_ring_create("Output_ring", SCHED_TX_RING_SZ,rte_socket_id(), RING_F_SC_DEQ | RING_F_SP_ENQ);if (tx_ring == NULL)rte_exit(EXIT_FAILURE, "Cannot create output ring\n");struct lcore_params p;p.rx_ring = rx_ring;p.tx_ring = tx_ring;RTE_LCORE_FOREACH_SLAVE(lcore_id) {if(lcore_id == 1)rte_eal_remote_launch((lcore_function_t*)lcore_tx, (void*)tx_ring, lcore_id);elserte_eal_remote_launch((lcore_function_t*)lcore_worker, (void*)&p, lcore_id);}lcore_rx(rx_ring);rte_eal_mp_wait_lcore();return 0;
}

接收数据包

0号lcore运行rx线程,其负责从网络接口接收DNS查询请求的UDP数据包。将接收到的数据包放入环形队列(rx_ring)中,以便后续处理模块消费。

static int lcore_rx(struct rte_ring *rx_ring){uint16_t port;uint16_t nb_rx, nb_tx; uint16_t total=0;struct rte_mbuf *bufs[BURST_SIZE];// 检查端口和轮询线程是否位于相同的NUMA节点if (rte_eth_dev_socket_id(port) > 0 &&rte_eth_dev_socket_id(port) !=(int)rte_socket_id())printf("WARNING, port %u is on remote NUMA node to ""polling thread.\n\tPerformance will ""not be optimal.\n", port);printf("\nCore %u doing packet RX.\n", rte_lcore_id());port=0;uint32_t rx_queue_drop_packets = 0;while(!force_quit){nb_rx = rte_eth_rx_burst(port, 0, bufs, BURST_SIZE);total+=nb_rx;nb_tx = rte_ring_enqueue_burst(rx_ring, (void *)bufs, nb_rx, NULL);if (unlikely(nb_tx < nb_rx)){rx_queue_drop_packets+=nb_rx-nb_tx; // 丢包while (nb_tx < nb_rx) {rte_pktmbuf_free(bufs[nb_tx++]);}}}printf("rx queue enqeue packet number: %d\n",total);printf("rx queue drop packet number: %d\n", rx_queue_drop_packets);
}

DNS请求处理

使用核心2和3,从 rx_ring 中取包,解析并处理DNS查询,生成响应后将其放入 tx_ring。

static int lcore_worker(struct lcore_params *p)
{uint16_t nb_rx, nb_tx;struct rte_mbuf *query_buf[PROCESS_SIZE], *reply_buf[PROCESS_SIZE]; struct rte_ring *in_ring = p->rx_ring;  // 输入环形队列struct rte_ring *out_ring = p->tx_ring; // 输出环形队列uint8_t *buffer;  // 指向数据部分的指针struct Message msg;  // 用于存储 DNS 消息的结构体memset(&msg, 0, sizeof(struct Message));  // 初始化消息结构体为0printf("\nCore %u doing packet processing.\n", rte_lcore_id());uint16_t tx_queue_drop_packets = 0;  // 用于统计传输队列中丢包的数量uint16_t total_dns_packet=0;while (!force_quit) {  for(uint16_t i = 0; i < PROCESS_SIZE; i++){do{reply_buf[i] = rte_pktmbuf_alloc(mbuf_pool);  // 分配 mbuf 内存,如果失败则重试}while(reply_buf[i] == NULL);}// 从输入环形队列中取出批量查询包nb_rx = rte_ring_dequeue_burst(in_ring,(void *)query_buf, PROCESS_SIZE, NULL);// 如果没有接收到包,释放刚刚分配的回复包内存,继续下一次循环if (unlikely(nb_rx == 0)){for(uint16_t i = 0; i < PROCESS_SIZE; i++)rte_pktmbuf_free(reply_buf[i]);continue;}uint16_t nb_tx_prepare = 0;  // 用于统计准备好发送的回复包数量for(uint16_t i = 0; i < nb_rx; i++){free_questions(msg.questions);free_resource_records(msg.answers);free_resource_records(msg.authorities);free_resource_records(msg.additionals);memset(&msg, 0, sizeof(struct Message));struct rte_ether_hdr *eth_hdr = rte_pktmbuf_mtod(query_buf[i], struct rte_ether_hdr *);if(*rte_pktmbuf_mtod_offset(query_buf[i], uint16_t*, 36) != rte_cpu_to_be_16(9000)){continue;}buffer = rte_pktmbuf_mtod_offset(query_buf[i], uint8_t*, 42); if (decode_msg(&msg, buffer, query_buf[i]->data_len - 42) != 0) {continue;}resolver_process(&msg);rte_pktmbuf_append(reply_buf[nb_tx_prepare], sizeof(struct rte_ether_hdr));rte_pktmbuf_append(reply_buf[nb_tx_prepare], sizeof(struct rte_ipv4_hdr));rte_pktmbuf_append(reply_buf[nb_tx_prepare], sizeof(struct rte_udp_hdr));uint8_t *p = buffer;if (encode_msg(&msg, &p) != 0) {continue;}uint32_t buflen = p - buffer;char * payload = (char*)rte_pktmbuf_append(reply_buf[nb_tx_prepare], buflen);rte_memcpy(payload, buffer, buflen); build_packet(rte_pktmbuf_mtod_offset(query_buf[i], char*, 0), rte_pktmbuf_mtod_offset(reply_buf[nb_tx_prepare], char*, 0), buflen);nb_tx_prepare++;}nb_tx = rte_ring_enqueue_burst(out_ring, (void *)reply_buf, nb_tx_prepare, NULL);total_dns_packet+=nb_tx;for(uint16_t i = 0; i < nb_rx; i++)rte_pktmbuf_free(query_buf[i]);for(uint16_t i = nb_tx; i < nb_tx_prepare; i++){tx_queue_drop_packets += 1;  // 统计未成功发送的回复包数量rte_pktmbuf_free(reply_buf[i]);  // 释放未成功发送的回复包}}printf("core %d: tx queue drop packet number: %d\n", rte_lcore_id(), tx_queue_drop_packets);printf("total sent dns packet is %d\n",total_dns_packet);return 0;
}

转发数据包

负责从发送队列(tx_ring)中获取已经生成的DNS响应包,并通过网卡将其发送回客户端。

static int lcore_tx(struct rte_ring *tx_ring)
{uint16_t port = 0;uint16_t nb_rx, nb_tx;struct rte_mbuf *bufs[BURST_SIZE];printf("\nCore %u doing packet TX.\n", rte_lcore_id());uint16_t dpdk_send_ring_drop_packets = 0;uint16_t total_sent = 0;while (!force_quit) {nb_rx = rte_ring_dequeue_burst(tx_ring, (void *)bufs, BURST_SIZE, NULL);nb_tx = rte_eth_tx_burst(port, 0, bufs, nb_rx);total_sent += nb_tx;if(unlikely(nb_tx < nb_rx)){dpdk_send_ring_drop_packets += nb_rx - nb_tx;while(nb_tx < nb_rx){rte_pktmbuf_free(bufs[nb_tx++]);}}}printf("dpdk send ring drop packet numbers: %d, total sent number: %d\n", dpdk_send_ring_drop_packets, total_sent);return 0;
}

测试结果

自定义一个域名和IP作为测试程序

uint8_t ip[4] = {192, 168, 1, 1};
add_A_record("foo.bar.com",ip);

使用以下代码完成DNS发包

from scapy.all import Ether, IP, UDP, DNS, DNSQR, sendp, RandShort, get_if_list# 设置目标IP和端口
target_ip = "192.168.131.153"
target_port = 9000# 源IP和MAC地址(修改为VMnet8的地址)
source_ip = "192.168.131.1"  # VMnet8 网卡的 IP 地址
source_mac = "00:50:56:C0:00:08"  # VMnet8 网卡的 MAC 地址# 构造DNS查询包
dns_query = DNS(rd=1, qd=DNSQR(qname="foo.bar.com", qtype="A"))
ip_packet = IP(src=source_ip, dst=target_ip)
udp_packet = UDP(sport=RandShort(), dport=target_port)
packet = ip_packet / udp_packet / dns_query
# 发送包,目的MAC地址设置为指定的以太网地址
sendp(Ether(src=source_mac, dst="00:0c:29:00:04:4d") / packet,iface="VMware Network Adapter VMnet8")

wireshark 抓包分析如下:

在这里插入图片描述

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

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

相关文章

力扣46.全排列

一、题目 二、代码 class Solution {int[] nums;List<List<Integer>> ans new ArrayList<>();List<Integer> path new ArrayList<>();boolean[] onPath;public List<List<Integer>> permute(int[] nums) {this.nums nums;int n …

【GUI设计】基于图像分割的GUI系统(3),matlab实现

博主简介&#xff1a;matlab图像代码项目合作&#xff08;扣扣&#xff1a;3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于Matlab的图像处理GUI系统&#xff08;3&#xff09;&#xff0c;用matlab实现。…

AH2212-12V转4.2V充电芯片

AH2212——12V转4.2V充电芯片&#xff0c;峰值2A输出编程电流&#xff0c;实现精准同步开关降压锂电池充电 随着科技的不断发展&#xff0c;移动电源、智能穿戴、电动工具等设备的应用越来越广泛&#xff0c;对电池充电芯片的需求也日益增大。本文将为您介绍一款高性能的充电芯…

与时间函数相关的那些事

在LuatOS中&#xff0c;获取时间函数用得最多的就是os.time()函数了。 接下来&#xff0c;我会讲一些与这个函数以及其他时间函数相关的知识。 一、时间戳相关 os.time()这个函数&#xff0c;只能获取当前时间戳&#xff1b;如果客户希望获取的是当前时间&#xff0c;即相应…

2024年【危险化学品生产单位安全生产管理人员】考试及危险化学品生产单位安全生产管理人员考试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年危险化学品生产单位安全生产管理人员考试为正在备考危险化学品生产单位安全生产管理人员操作证的学员准备的理论考试专题&#xff0c;每个月更新的危险化学品生产单位安全生产管理人员考试题祝您顺利通过危险化…

开源实时多模态AI聊天机器人Moshi,语音对话延迟低至200毫秒!

开源实时多模态AI聊天机器人Moshi&#xff0c;语音对话延迟低至200毫秒&#xff01; 最近AI圈真是热闹非凡&#xff0c;继Meta发布Llama 3之后&#xff0c;各种开源大模型也是层出不穷。这不&#xff0c;法国一个非盈利AI研究实验室Kyutai&#xff0c;又搞了个大新闻&#xff0…

教你如何调用微信公众号模板消息发送接口

文章目录 前言准备工作代码实现获取accessToken调用模板消息发送接口前言 本文带你理解微信公众号模板消息发送接口的调用,面向的场景是你需要对你的公众号或者小程序用户发送公众号通知消息,没错,就算是小程序也是通过关联公众号,并且用户使用小程序时跳到公众号关注页关注…

C++ 进阶之路:非类型模板参数、模板特化与分离编译详解

目录 非类型模版参数 类型模板参数 非类型模板参数 非类型模板参数的使用 模板的特化 函数模板的特化 类模板的特化 全特化与偏特化 偏特化的其它情况 模板的分离编译 什么是分离编译 为什么要分离编译 为什么模板不能分离编译 普通的类和函数都是可以分离编译的…

那年我双手插兜,使用IPv6+DDNS动态域名解析访问NAS

估计有很多科技宅和我一样&#xff0c;会买一个NAS存储或者自己折腾刷一下黑群晖玩玩&#xff0c;由于运营商不给分配固定的公网IP&#xff0c;就导致我在外出的时候无法访问家里的NAS&#xff0c;于是远程访问常常受到IP地址频繁变动的困扰。为了解决这一问题&#xff0c;结合…

面试知识点总结篇一

一、C语言和C有什么区别 C语言是面向过程&#xff0c;强调用函数将问题分解为多个子任务&#xff0c;按顺序逐步进行。数据和操作分开C则是面向对象&#xff0c;面向对象是一种基于对象和类的编程范式&#xff0c;关注如何利用对象来抽象和模拟现实世界的实体。因此引入了类&a…

docker部署datart并添加扩展clickhouseodps的jar包数据源驱动

近期部门有个小需求&#xff0c;针对所有产品线的用户访问记录日志需要一个看板展示&#xff0c;于是在找有没有开源的项目不用自己开发的产品直接部署&#xff0c;千挑万选发现一个叫datart的产品能自定义编写sql展示想要展示的数据&#xff0c;于是开始了datart的搭建部署&am…

大厂真题-Kafka为什么这么快之零拷贝

一、零拷贝技术的背景 在传统的数据传输过程中&#xff0c;当需要将磁盘中的数据发送到远程服务器时&#xff0c;数据通常需要经过多次拷贝和上下文切换。具体来说&#xff0c;这些步骤包括&#xff1a; 四次拷贝 从硬盘到内核缓冲区&#xff1a; 当用户进程通过read()系统调…

【HTML5】html5开篇基础(2)

1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; Hello, Hello~ 亲爱的朋友们&#x1f44b;&#x1f44b;&#xff0c;这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章&#xff0c;请别吝啬你的点赞❤️❤️和收藏&#x1f4d6;&#x1f4d6;。如果你对我的…

Python中requests模块(爬虫)基本使用

Python的requests模块是一个非常流行的HTTP库&#xff0c;用于发送HTTP/1.1请求。 一、模块导入 1、requests模块的下载&#xff1a; 使用包管理器下载&#xff0c;在cmd窗口&#xff0c;或者在项目的虚拟环境目录下&#xff1a; pip3 install -i https://pypi.tuna.tsingh…

DC-DC选型

Buck、Boost、Buck-boost 同步非同步 隔离与非隔离 电源效率 模式选择 选型 总结

【机器学习】TensorFlow编程基础

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 TensorFlow编程基础张量(Tensor)计算图(Computational Graph)会话(Session)基本…

数据包签名校验的Web安全测试实践

01 测试场景 在金融类的Web安全测试中&#xff0c;经常可以见到Web请求和响应数据包加密和签名保护&#xff0c;由于参数不可见&#xff0c;不能重放请求包&#xff0c;这类应用通常不能直接进行有效的安全测试&#xff0c;爬虫也爬不到数据。 02 解决思路 对于这类应用&am…

STaR: Bootstrapping Reasoning With Reasoning

STaR: Bootstrapping Reasoning With Reasoning 基本信息 博客贡献人 燕青 作者 Eric Zelikman, Yuhuai Wu, Jesse Mu, et al. from Stanford University and Google Research 标签 Large Language Model, Chain-of-thought, Fine-tuning 摘要 生成逐步的“思维链”逻…

揭秘!高校如何逆袭,在算法与科技竞技场中脱颖而出?

目录 揭秘!高校如何逆袭,在算法与科技竞技场中脱颖而出? 一、算法秘境:深度挖掘,教学相长 二、跨界融合:场景为王,合作共赢 企业和高校之间在:场景,算法,数据,算力的优势,高校优势不明显,仅仅在算法方面存在一些优势但并不明显。高校怎样做 揭秘!高校如何逆袭…

2024最新盘点:国内外主流的10款流程管理系统!

本文将盘点十款流程管理系统&#xff0c;为企业选型提供参考&#xff01; 想象一下&#xff0c;在一个企业中&#xff0c;各个部门的工作流程混乱&#xff0c;审批环节繁琐&#xff0c;信息传递不及时。这时&#xff0c;流程管理系统就如同一位高效的指挥官&#xff0c;将企业的…