一、丢包
1.丢包原因
- 网络拥塞:流量过多导致队列满
- 硬件限制:网卡处理能力或配置问题
- 软件瓶颈:应用程序处理速度跟不上流量
- 配置错误:DPDK或网络设置错误
二、DPDK检测丢包
- DPDK内置统计功能
- 端口统计:通过rte_eth_stats_get()函数来获取这些统计信息。
- 队列统计:通过获取和打印队列级别的统计信息来监控丢包情况
- 使用环形缓冲区(Ring-Buffer统计):实现一个环形缓冲区来跟踪丢包事件。例如,当接收环或发送环已满时,可以增加一个丢包计数器:
三、代码实现
DPDK内置统计功能:端口统计
struct rte_eth_stats stats;
rte_eth_stats_get(port_id, &stats);
printf("Packets Received: %lu\n", stats.ipackets);
printf("Packets Transmitted: %lu\n", stats.opackets);
printf("Packets Dropped: %lu\n", stats.imissed + stats.oerrors);
DPDK内置统计功能:队列统计
void check_queue_stats(uint16_t port_id) {
struct rte_eth_stats eth_stats;
struct rte_eth_stats prev_stats[NUM_QUEUES] = {0};
// Get port statistics
if (rte_eth_stats_get(port_id, ð_stats) != 0) {
printf("Failed to get statistics for port %" PRIu16 "\n", port_id);
return;
}
// Loop through each queue
for (uint16_t queue_id = 0; queue_id < NUM_QUEUES; ++queue_id) {
uint64_t rx_dropped = eth_stats.q_ipackets[queue_id] - prev_stats[queue_id].q_ipackets[queue_id];
uint64_t tx_dropped = eth_stats.q_opackets[queue_id] - prev_stats[queue_id].q_opackets[queue_id];
printf("Port %" PRIu16 " Queue %" PRIu16 " RX Packets: %" PRIu64 "\n", port_id, queue_id, eth_stats.q_ipackets[queue_id]);
printf("Port %" PRIu16 " Queue %" PRIu16 " TX Packets: %" PRIu64 "\n", port_id, queue_id, eth_stats.q_opackets[queue_id]);
printf("Port %" PRIu16 " Queue %" PRIu16 " RX Dropped: %" PRIu64 "\n", port_id, queue_id, eth_stats.q_errors[queue_id]);
printf("Port %" PRIu16 " Queue %" PRIu16 " TX Dropped: %" PRIu64 "\n", port_id, queue_id, eth_stats.q_errors[queue_id]);
// Update previous statistics
prev_stats[queue_id] = eth_stats;
}
}
使用环形缓冲区(Ring-Buffer统计)
int dropped_packets=0
if (rte_ring_enqueue(rx_ring, packet) < 0) {
dropped_packets++;
}
四、自定义Tcp协议检测丢包
检测丢包的方法
- 序列号分析:通过分析序列号和确认号来识别丢包。如果一个具有较高序列号的数据包在具有较低序列号的数据包之前到达,就可以推断出有数据包丢失。
- 检测重复ACK:当发送方收到连续三个或以上的重复ACK时,它假定数据包丢失。
- ACK超时:为期望的数据包设置超时。如果数据包在指定时间内未到达,则认为其丢失
- 负确认(NACK):使用NACK来显式地指示丢失的数据包,当检测到序列中断时发送NACK
丢包检测机制流程
根据序列号
- 为每个流或连接维护最后一次看到的序列号的记录。
- 在接收数据包时,将序列号与上一次的序列号进行比较。如果有间隙,则检测到数据包丢失。
- 处理丢包,例如重传或调整拥塞窗口大小.
检测重复ACK
- 维护一个ACK序列号计数器。
- 当收到相同的ACK时,增加计数
- 如果计数超过阈值(如3次重复ACK),则触发快速重传
ACK超时
- 为每个发送的数据包启动一个计时器。
- 在预期时间内未收到ACK时,假设数据包丢失并执行重传。
五、自定义tcp协议检测丢包代码实现
根据序列号
// 丢包检测逻辑
void detect_packet_loss(struct rte_mbuf *bufs[], uint16_t nb_rx) {
struct rte_ipv4_hdr *ip_hdr;
struct rte_tcp_hdr *tcp_hdr;
uint32_t last_seq = 0;
uint32_t current_seq;
for (int i = 0; i < nb_rx; i++) {
struct rte_mbuf *buf = bufs[i];
// 提取IP头部
ip_hdr = rte_pktmbuf_mtod_offset(buf, struct rte_ipv4_hdr *, sizeof(struct rte_ether_hdr));
// 检查是否为TCP数据包
if (ip_hdr->next_proto_id == IPPROTO_TCP) {
// 提取TCP头部
tcp_hdr = (struct rte_tcp_hdr *)((unsigned char *)ip_hdr + sizeof(struct rte_ipv4_hdr));
// 获取当前序列号
current_seq = rte_be_to_cpu_32(tcp_hdr->sent_seq);
// 检测丢包
if (last_seq != 0 && current_seq != last_seq + 1) {
printf("Packet loss detected! Expected seq: %u, received seq: %u\n", last_seq + 1, current_seq);
}
// 更新最后的序列号
last_seq = current_seq;
}
rte_pktmbuf_free(buf);
}
}
检测重复ACK
int duplicate_ack_count = 0;
void process_ack_packet(struct rte_mbuf *mbuf,uint32_t ack_num) {
struct rte_ether_hdr *eth_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
struct rte_ipv4_hdr *ip_hdr = (struct rte_ipv4_hdr *)((char *)eth_hdr + sizeof(struct rte_ether_hdr));
struct rte_tcp_hdr *tcp_hdr = (struct rte_tcp_hdr *)((char *)ip_hdr + sizeof(struct rte_ipv4_hdr));
if ((tcp_hdr->tcp_flags & RTE_TCP_ACK_FLAG) != 0) {
ack_num = rte_be_to_cpu_32(tcp_hdr->recv_ack);
printf("ACK detected: %u\n", ack_num);
}
}
void handle_ack(uint32_t ack_num,uint32_t last_ack) {
if (ack_num == last_ack) {
duplicate_ack_count++;
if (duplicate_ack_count >= DUP_ACK_THRESHOLD) {
printf("Duplicate ACK detected! Retransmission needed for sequence number: %u\n", ack_num);
// 执行重传逻辑
duplicate_ack_count = 0; // 重置计数器
}
} else {
last_ack = ack_num;
duplicate_ack_count = 0; // 重置计数器
}
}
ACK超时
#include <rte_timer.h>
struct rte_timer my_timer;
uint64_t hz = rte_get_timer_hz(); // 获取系统时钟频率
//定义一个回调函数,当超时时调用
void timer_callback(struct rte_timer *tim, void *arg) {
printf("Timer expired, retransmitting packet...\n");
// 在这里执行重传逻辑
}
unsigned lcore_id = rte_lcore_id(); // 获取当前核心ID
uint64_t timeout_cycles = hz; // 1秒的超时
rte_timer_init(&my_timer);
rte_timer_reset(&my_timer, timeout_cycles, PERIODICAL, lcore_id, timer_callback, NULL);