如何在DPDK报文中解析eDNS并提取Flags、Rdata等指定字段
DPDK DNS报文字段提取实现方案
现有代码问题
- 你在外层定义了
struct dnshdr *dns_hdr,但在if分支内重复定义了局部变量char *dns_hdr,外层指针未被赋值,后续操作会触发空指针访问 - 未做报文长度校验,直接偏移取DNS头可能越界,DPDK场景下必须先确认payload长度大于UDP头+DNS头总长度再操作
- DNS是变长结构,除固定头外还有查询、回答等可变段,不能硬编码偏移取Rdata、TTL字段,需要先处理域名压缩指针
C语言DPDK原生实现方案
1. 定义DNS核心结构
/* DNS固定头部 */ struct dnshdr { uint16_t id; uint16_t flags; /* 待提取Flags字段 */ uint16_t qdcount; /* 查询数量 */ uint16_t ancount; /* 回答数量 */ uint16_t nscount; uint16_t arcount; } __attribute__((packed)); /* DNS回答段固定部分 */ struct dns_rr { uint16_t type; uint16_t class; /* 待提取Class字段 */ uint32_t ttl; /* 待提取TTL字段 */ uint16_t rdlength; /* Rdata长度 */ /* 后续为rdlength长度的Rdata字段 */ } __attribute__((packed));
2. 实现DNS解析逻辑
#include <rte_mbuf.h> #include <rte_udp.h> #include <rte_ip.h> /* 辅助函数:跳过DNS域名(兼容压缩指针),返回下一个字段的指针,出错返回NULL */ static uint8_t* skip_dns_name(uint8_t *ptr, uint8_t *end) { while (ptr < end) { uint8_t len = *ptr; if (len == 0) return ptr + 1; /* 处理压缩指针:最高两位为1 */ if ((len & 0xC0) == 0xC0) return ptr + 2; ptr += len + 1; } return NULL; } /** * 解析DNS报文,提取目标字段 * 返回0为解析成功,负数为解析失败 */ int parse_dns_pkt(struct rte_mbuf *pkt, struct rte_udp_hdr *udp_hdr, uint16_t *out_flags, uint16_t *out_class, uint32_t *out_ttl, uint8_t **out_rdata, uint16_t *out_rdlen) { /* 校验报文最小长度 */ if (rte_pktmbuf_data_len(pkt) < (sizeof(struct rte_udp_hdr) + sizeof(struct dnshdr))) { return -1; } struct dnshdr *dns_h = (struct dnshdr *)((uint8_t *)udp_hdr + sizeof(struct rte_udp_hdr)); *out_flags = rte_be_to_cpu_16(dns_h->flags); /* 无回答的查询包直接返回,可根据需求调整逻辑 */ if (rte_be_to_cpu_16(dns_h->ancount) == 0) { return 0; } uint8_t *ptr = (uint8_t *)(dns_h + 1); uint8_t *pkt_end = (uint8_t *)udp_hdr + rte_be_to_cpu_16(udp_hdr->dgram_len); /* 跳过查询段 */ ptr = skip_dns_name(ptr, pkt_end); if (!ptr || ptr + 4 > pkt_end) return -2; ptr += 4; // 跳过查询的type、class字段 /* 解析第一个回答段 */ ptr = skip_dns_name(ptr, pkt_end); if (!ptr || ptr + sizeof(struct dns_rr) > pkt_end) return -3; struct dns_rr *rr = (struct dns_rr *)ptr; *out_class = rte_be_to_cpu_16(rr->class); *out_ttl = rte_be_to_cpu_32(rr->ttl); *out_rdlen = rte_be_to_cpu_16(rr->rdlength); *out_rdata = ptr + sizeof(struct dns_rr); if (*out_rdata + *out_rdlen > pkt_end) return -4; return 0; }
3. 业务侧调用
替换你原有的端口判断逻辑即可:
// 同时匹配DNS请求和响应包 if (rte_be_to_cpu_16(udp_hdr->dst_port) == 53 || rte_be_to_cpu_16(udp_hdr->src_port) == 53) { printf("DNS Packet detected\n"); uint16_t dns_flags, dns_class, rd_len; uint32_t dns_ttl; uint8_t *rdata; if (parse_dns_pkt(pkt, udp_hdr, &dns_flags, &dns_class, &dns_ttl, &rdata, &rd_len) == 0) { // 提取到的字段可直接存入自定义结构体/数组独立保存 printf("DNS Flags: 0x%x, Class: %d, TTL: %u\n", dns_flags, dns_class, dns_ttl); // Rdata可通过rte_memcpy拷贝到独立缓冲区存储 } }
C++封装实现方案
如果使用C++可以封装为类,自动管理字段生命周期:
#include <vector> #include <cstdint> #include <rte_mbuf.h> #include <rte_udp.h> class DnsParser { public: uint16_t flags; uint16_t cls; uint32_t ttl; std::vector<uint8_t> rdata; bool parse(struct rte_mbuf* pkt, struct rte_udp_hdr* udp_hdr) { // 解析逻辑和C版本一致,解析成功后将Rdata拷贝到vector即可 uint16_t rd_len; uint8_t* rdata_ptr; // ... 省略重复解析逻辑 rdata.resize(rd_len); memcpy(rdata.data(), rdata_ptr, rd_len); return true; } };
注意:所有网络字节序的字段必须通过rte_be_to_cpu_*系列接口转换为主机字节序后再存储使用
内容的提问来源于stack exchange,提问作者user3150710
相关产品推荐
相关产品推荐

