使用eBPF过滤数据包时出现丢包及目标主机不可达问题求助
eBPF XDP 数据包过滤导致ICMP丢包及目标不可达问题解决
问题场景
使用以下eBPF代码进行数据包过滤时,执行ping操作仅能传输部分数据包,随后出现「Destination Host Unreachable」提示,最终统计显示40%的丢包率。
原eBPF代码
SEC("xdp") int icmp_timestamp(struct xdp_md *xdp) { void *data_end = (void *)(long)xdp->data_end; void *data = (void *)(long)xdp->data; if (data + sizeof(struct ethhdr) <= data_end) { struct ethhdr *eth = data; if (eth->h_proto == htons(ETH_P_IP) && data + sizeof(struct ethhdr) + sizeof(struct iphdr) <= data_end) { struct iphdr *ip = data + sizeof(struct ethhdr); if (ip->protocol == IPPROTO_ICMP) { // Do some processing for ICMP packets // Allow the packet to pass return XDP_PASS; } } } return XDP_DROP; }
ping操作输出
len=46 ip=10.0.2.6 ttl=63 id=29041 icmp_seq=2 rtt=8.5 ms len=46 ip=10.0.2.6 ttl=63 id=29172 icmp_seq=3 rtt=7.1 ms len=46 ip=10.0.2.6 ttl=63 id=29276 icmp_seq=4 rtt=5.8 ms len=46 ip=10.0.2.6 ttl=63 id=29496 icmp_seq=5 rtt=8.9 ms len=46 ip=10.0.2.6 ttl=63 id=29567 icmp_seq=6 rtt=4.6 ms len=46 ip=10.0.2.6 ttl=63 id=29721 icmp_seq=7 rtt=7.2 ms len=46 ip=10.0.2.6 ttl=63 id=29728 icmp_seq=8 rtt=10.1 ms From 10.0.2.5 icmp_seq=10 Destination Host Unreachable From 10.0.2.5 icmp_seq=11 Destination Host Unreachable From 10.0.2.5 icmp_seq=12 Destination Host Unreachable From 10.0.2.5 icmp_seq=13 Destination Host Unreachable From 10.0.2.5 icmp_seq=14 Destination Host Unreachable From 10.0.2.5 icmp_seq=15 Destination Host Unreachable 10.0.2.6 hping statistic... 15 packets transmitted, 9 packets received, 40% packet loss round-trip min/avg/max= 2.9/7.1/10.1 ms
问题根源
- ARP包被丢弃:原代码仅放行IP协议中的ICMP包,ARP协议(
ETH_P_ARP)数据包被直接丢弃。当系统ARP缓存过期后,无法发送ARP请求获取目标主机MAC地址,导致后续ping包无法正常发送,触发目标不可达提示。 - IP头长度处理错误:代码直接用
sizeof(struct iphdr)计算IP头长度,但IP头可能包含可变长度的选项(由ip->ihl字段指定,单位为4字节)。遇到带选项的IP包(如部分ICMP错误响应包)时,边界检查会失败,导致这些包被错误丢弃。
修复后的代码
#include <linux/if_ether.h> #include <linux/ip.h> #include <linux/icmp.h> #include <linux/bpf.h> #include <bpf/bpf_helpers.h> SEC("xdp") int icmp_timestamp(struct xdp_md *xdp) { void *data_end = (void *)(long)xdp->data_end; void *data = (void *)(long)xdp->data; // 检查以太网头完整性 if (data + sizeof(struct ethhdr) > data_end) { return XDP_DROP; } struct ethhdr *eth = data; // 放行ARP包,保证MAC地址解析正常 if (eth->h_proto == htons(ETH_P_ARP)) { return XDP_PASS; } // 处理IP协议包 if (eth->h_proto == htons(ETH_P_IP)) { // 检查IP头最小长度是否完整 if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) > data_end) { return XDP_DROP; } struct iphdr *ip = data + sizeof(struct ethhdr); // 计算实际IP头长度(含选项),检查边界 __u32 ip_hdr_len = ip->ihl * 4; if (data + sizeof(struct ethhdr) + ip_hdr_len > data_end) { return XDP_DROP; } // 放行所有ICMP包 if (ip->protocol == IPPROTO_ICMP) { // 自定义处理逻辑可在此添加 return XDP_PASS; } } // 丢弃其他非必要数据包 return XDP_DROP; } char _license[] SEC("license") = "GPL";
修复说明
- 新增ARP包放行逻辑,确保ARP请求与响应正常传输,维持ARP缓存有效性,避免因MAC解析失败导致的数据包发送失败。
- 改用
ip->ihl * 4计算实际IP头长度,正确处理带选项的IP包,避免边界检查错误导致的合法数据包被丢弃。 - 调整边界检查顺序,先验证最小长度再处理可变长度,提升代码健壮性。
内容的提问来源于stack exchange,提问作者Manideep G
相关产品推荐
相关产品推荐

