DPDK是一个用户空间的高性能数据包处理框架,可以用于以太网接口的快速报文处理。DPDK的分片重组功能能够将大于MTU的报文分片进行重组,以达到提高数据传输效率的目的。
DPDK分片重组的实现原理是在内存中维护一个待重组的缓存池,对接收到的分片进行缓存,等到所有的分片数据到达后进行重组,然后再将重组好的大数据报送往网络协议栈进行后续处理。
下面是DPDK的分片重组示例代码,目的是将数据报分片后,再进行重组:
#include <rte_ethdev.h>
#define NB_MBUF 8192
#define MAX_PACKET_SZ 2048
#define MBUF_CACHE_SIZE 256
struct mbuf_table {
uint16_t len;
struct rte_mbuf *m_table[MAX_PACKET_SZ];
};
struct pkt_seg {
struct rte_mbuf *pkt;
struct rte_mbuf *seg;
uint32_t offset;
};
struct reassemble_data {
uint32_t hash;
uint32_t next_expected_seq;
uint32_t nb_seg;
struct pkt_seg seg[3];
};
struct mbuf_table mb_tbl[NB_MBUF];
struct reassemble_data reas_data[NB_MBUF];
/* 初始化由RTE mempool分配的缓存池 */
void init_mbuf_pool(){
struct rte_mempool *mbuf_pool;
mbuf_pool = rte_pktmbuf_pool_create("mbuf_pool", NB_MBUF * MAX_PACKET_SZ,
MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
}
/* 分片缓存 */
int cache_reassemble_packet(struct rte_mbuf *pkt, struct rte_mbuf *seg) {
uint32_t hash;
uint32_t next_expected_seq;
uint32_t nb_seg;
struct pkt_seg *pkt_seg = NULL;
uint16_t i;
struct reassemble_data *reas_data_entry = NULL;
uint32_t offset = seg->pkt.data_len - sizeof(struct ipv4_hdr) -
sizeof(struct udp_hdr);
if (offset == 0){
/* 不需要重组 */
return -1;
}
hash = get_hash(pkt, seg);
int i;
for (i = 0; i < NB_MBUF; i++) {
reas_data_entry = &reas_data[i];
if (reas_data_entry->hash == hash){
break;
}
}
if (i < NB_MBUF) {
/* 数据包已缓存,进行分片重组过程 */
struct pkt_seg *pkt_seg;
next_expected_seq = reas_data_entry->next_expected_seq;
nb_seg = reas_data_entry->nb_seg;
for (i = 0; i < nb_seg; i++) {
pkt_seg = &reas_data_entry->seg[i];
if (pkt_seg->offset == (next_expected_seq - offset)){
/* 当前分片匹配缓存中已存分片,进行重组 */
reas_data_entry->next_expected_seq = next_expected_seq +
pkt_seg->seg->pkt.data_len -
sizeof(struct ipv4_hdr) -
sizeof(struct udp_hdr);
pkt_seg->pkt->pkt.data_len = reas_data_entry->next_expected_seq;
return reas_data_entry->next_expected_seq;
}
}
if (reas_data_entry->nb_seg < 3) {
/* 将当前分片加入缓存中 */
pkt_seg = &reas_data_entry->seg[nb_seg];
pkt_seg->offset = pkt->pkt.data_len - sizeof(struct ipv4_hdr) -
sizeof(struct udp_hdr);
pkt_seg->pkt = pkt;
pkt_seg->seg = seg;
reas_data_entry->nb_seg++;
}
} else {
/* 缓存中不存在当前数据包 */
reas_data_entry = &reas_data[i];
reas_data_entry->hash = hash;
pkt_seg = &reas_data_entry->seg[0];
pkt_seg->offset = pkt->pkt.data_len - sizeof(struct ipv4_hdr) -
sizeof(struct udp_hdr);
pkt_seg->pkt = pkt;
pkt_seg->seg = seg;
reas_data_entry->next_expected_seq = pkt_seg->seg->pkt.data_len -
sizeof(struct ipv4_hdr) -
sizeof(struct udp_hdr);
reas_data_entry->nb_seg = 1;
}
return -1;
}
int main(int argc, char **argv) {
int ret;
uint8_t portid = 0;
struct rte_mbuf *pkts_burst[MAX_PKT_BURST];
/* 初始化EAL以及DPDK */
ret = rte_eal_init(argc, argv);
if (ret < 0)
rte_panic("Cannot init EAL\n");
argc -= ret;
argv += ret;
ret = rte_pmd_init_all();
if (ret < 0)
rte_exit(EXIT_FAILURE, "Cannot init pmds\n");
/* 初始化由RTE mempool分配的缓存池 */
init_mbuf_pool();
while (1) {
uint16_t nb_rx = rte_eth_rx_burst(portid, 0, pkts_burst, MAX_PKT_BURST);
for (int i = 0; i < nb_rx; i++) {
struct rte_mbuf *pkt = pkts_burst[i];
if (pkt->pkt.nb_segs > 1) {
/* 大于MTU的报文分片 */
uint32_t ret = cache_reassemble_packet(pkt, pkt->pkt.next);
if (ret > 0) {
/* 如果ret的值大于0,则说明重组成功,传递给网络协议栈 */
continue;
} else if (ret == 0) {
rte_pktmbuf_free(pkt);
continue;
}
}
/* 如果不需要缓存/重组,则将数据包传递给协议栈 */
eth_protocol_handler(pkt);
}
}
return 0;
}
值得注意的是,DPDK的分片重组需要耗费大量的CPU和内存资源,因此在设计DPDK应用程序时应该仔细考虑内存和CPU的使用情况,以确保应用程序的高性能和稳定性。
Dpdk/网络协议栈/vpp/OvS/DDos/NFV 视频教程学习地址: https://ke.qq.com/course/5066203?flowToken=1043068
DPDK/网络虚拟化 相关学习资料、视频教程 学习群:739729163