使用eBPF XDP程序过滤数据包后遇网络拥塞及Ping不通问题
解决XDP eBPF程序挂载后无法Ping及网络拥塞问题
问题重现
我在VM中部署了以下eBPF程序,意图仅允许ICMP数据包通过并丢弃TCP数据包:
#include <linux/bpf.h> #include <linux/if_ether.h> #include <linux/ip.h> #include <linux/icmp.h> #include <netinet/in.h> #include <linux/udp.h> #include <linux/tcp.h> #include <linux/pkt_cls.h> #include <bpf/bpf_helpers.h> #include <linux/bpf_perf_event.h> struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 1); __type(key, int); __type(value, uint64_t); } timestamp_map SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 1); __type(key, int); __type(value, uint64_t); } counter_map SEC(".maps"); SEC("xdp") int icmp_timestamp(struct xdp_md *xdp) { // Load the Ethernet protocol void *data_end = (void *)(long)xdp->data_end; void *data = (void *)(long)xdp->data; struct ethhdr *eth = data; if (data + sizeof(struct ethhdr) > data_end) return XDP_DROP; // Process only IP packets if (eth->h_proto == htons(ETH_P_IP)) { if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) > data_end) return XDP_DROP; // Load the IP protocol struct iphdr *ip = data + sizeof(struct ethhdr); if (ip->protocol == IPPROTO_ICMP) { // Load the timestamp uint64_t timestamp = bpf_ktime_get_ns(); // Increment the packet counter int key_counter = 0; uint64_t *counter = bpf_map_lookup_elem(&counter_map, &key_counter); if (counter) { (*counter)++; } // Store the timestamp in the per-CPU map int key_timestamp = 0; bpf_map_update_elem(×tamp_map, &key_timestamp, ×tamp, BPF_ANY); // Allow the packet to pass return XDP_PASS; } } // Drop the packet for non-ICMP or non-IP packets return XDP_DROP; } char _license[] SEC("license") = "GPL";
程序编译加载成功后,执行命令sudo bpftool net attach xdp id 40 dev enp0s3将其挂载到XDP钩子及网卡enp0s3,但挂载后出现网络拥塞问题,且无法通过Ping传输数据。
问题分析
- IP头部边界检查错误:代码中用固定的
sizeof(struct iphdr)(20字节)判断IP头部长度,但IP头部实际长度由ip->ihl字段决定(单位为4字节)。若IP包带有头部选项(部分Ping包或虚拟机环境下的IP包可能包含),实际长度会超过20字节,此时边界检查会触发XDP_DROP,导致合法ICMP包被丢弃。 - XDP挂载模式兼容性:部分虚拟机网卡驱动不支持XDP的
drv(驱动级)模式,默认挂载时使用该模式会引发数据包处理异常。
解决方案
1. 修复IP头部边界检查逻辑
修改代码中的IP头部检查逻辑,用ip->ihl * 4计算实际头部长度,避免误判合法数据包:
SEC("xdp") int icmp_timestamp(struct xdp_md *xdp) { void *data_end = (void *)(long)xdp->data_end; void *data = (void *)(long)xdp->data; struct ethhdr *eth = data; if (data + sizeof(struct ethhdr) > data_end) return XDP_DROP; if (eth->h_proto == htons(ETH_P_IP)) { struct iphdr *ip = data + sizeof(struct ethhdr); // 先检查ip指针是否在数据范围内 if ((void *)ip + sizeof(*ip) > data_end) return XDP_DROP; // 计算实际IP头部长度并校验边界 uint32_t ip_hdr_len = ip->ihl * 4; if ((void *)ip + ip_hdr_len > data_end) return XDP_DROP; if (ip->protocol == IPPROTO_ICMP) { uint64_t timestamp = bpf_ktime_get_ns(); int key_counter = 0; uint64_t *counter = bpf_map_lookup_elem(&counter_map, &key_counter); if (counter) { (*counter)++; } int key_timestamp = 0; bpf_map_update_elem(×tamp_map, &key_timestamp, ×tamp, BPF_ANY); return XDP_PASS; } } return XDP_DROP; }
2. 切换XDP挂载模式
若虚拟机网卡不支持驱动级XDP,改用兼容性更好的skb(套接字缓冲区)模式挂载:
# 先卸载原有挂载 sudo bpftool net detach xdp dev enp0s3 # 用skb模式重新挂载 sudo bpftool net attach xdp skb id 40 dev enp0s3
3. 调试验证
- 检查计数器增长情况,确认ICMP包被正确识别:
# 获取counter_map的ID并查看内容 sudo bpftool map dump id $(sudo bpftool map list | grep counter_map | awk '{print $1}') - 查看XDP挂载状态:
sudo bpftool net list xdp dev enp0s3
内容的提问来源于stack exchange,提问作者Manideep G
相关产品推荐
相关产品推荐

