基于Linux XDP eBPF为UDP数据包添加时间戳的问题排查
XDP程序插入UDP时间戳失败及权限问题排查
问题描述
我尝试编写一个XDP程序,将时间戳插入UDP数据包的payload中,但未成功,怀疑是指针操作出现问题。数据包无校验和,我完全接受通过覆盖部分payload来容纳时间戳。我的目标是测量数据包在OVS入口与出口之间的延迟,使用的是CentOS Stream 9系统及xdp-loader工具。
使用sudo加载以下代码时,出现如下错误:
Current rlimit 8388608 already >= minimum 1048576
Loading 1 files on interface 'ens16f0np0'.
XDP program 0: Run prio: 50. Chain call actions: XDP_PASS
Couldn't attach XDP program on iface 'ens16f0np0': Permission denied(-13)
出错的XDP代码
#include <linux/bpf.h> #include "bpf_helpers.h" #include <linux/if_ether.h> #include <linux/ip.h> #include <linux/in.h> #include <linux/udp.h> #ifndef __bpf_htons #define __bpf_htons(x) ((__be16)___constant_swab16((x))) #endif SEC("udp_timestamp_xdp") int xdp_timestamp_func(struct xdp_md *ctx) { void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; struct iphdr *iph = (struct iphdr *)(eth + 1); struct udphdr *udph = (struct udphdr *)(iph + 1); __u64 timestamp = bpf_ktime_get_ns(); // Check if the packet is UDP if (eth->h_proto != __bpf_htons(ETH_P_IP) || iph->protocol != IPPROTO_UDP) return XDP_PASS; // Calculate the pointer to the UDP payload __u8 *payload = (__u8 *)(udph + 1); // Copy the timestamp into the UDP payload __builtin_memcpy(payload, ×tamp, sizeof(__u64)); // Adjust packet size if needed to accommodate the timestamp int ret = bpf_xdp_adjust_tail(ctx, sizeof(__u64)); if (ret < 0) { // Adjusting tail failed, pass the packet without modification return XDP_PASS; } return XDP_PASS; }
但问题并非sudo权限不足,因为我可以正常加载并使用以下丢弃所有数据包的程序:
可正常加载的XDP代码
#include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <linux/if_ether.h> #include <linux/ip.h> #include <linux/in.h> #include <linux/udp.h> #ifndef __bpf_htons #define __bpf_htons(x) ((__be16)___constant_swab16((x))) #endif SEC("udp_timestamp_xdp") int xdp_timestamp_func(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; struct iphdr *iph = (struct iphdr *)(eth + 1); struct udphdr *udph = (struct udphdr *)(iph + 1); __u64 timestamp = bpf_ktime_get_ns(); // Check if the packet contains enough data for UDP and timestamp if (udph + 1 > (struct udphdr *)data_end) return XDP_DROP; // Check if the packet is UDP if (eth->h_proto != __bpf_htons(ETH_P_IP) || iph->protocol != IPPROTO_UDP) return XDP_PASS; // Calculate the pointer to the UDP payload __u8 *payload = (__u8 *)(udph + 1); // Check if the payload length is sufficient if ((void *)(payload + sizeof(__u64)) > data_end) return XDP_DROP; // Copy the timestamp into the UDP payload __builtin_memcpy(payload, ×tamp, sizeof(__u64)); return XDP_PASS; }
请问我哪里操作出错了?
问题分析与解决建议
1. 权限拒绝的本质:BPF验证器拦截
你看到的Permission denied(-13)不是系统sudo权限问题,而是BPF验证器拒绝加载不符合安全规则的程序,xdp-loader将验证失败的错误包装成了权限拒绝提示。
2. 第一个代码的核心错误
- 缺少严格的边界检查:直接访问
eth + 1、iph + 1等指针时,未校验是否超过data_end,BPF验证器要求所有内存访问必须有边界防护,防止越界。 - 错误调用
bpf_xdp_adjust_tail:你明确要覆盖payload而非扩展数据包,但代码却试图调用该函数增加包长。这个操作不仅不需要,还需要特定硬件支持,直接触发验证器拦截。 - 逻辑顺序错误:先访问协议头字段,再做协议类型判断,违反了BPF验证器的内存访问安全逻辑。
3. 修正后的代码实现
基于你"覆盖UDP payload前8字节存时间戳"的需求,修正后的代码如下:
#include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <linux/if_ether.h> #include <linux/ip.h> #include <linux/in.h> #include <linux/udp.h> #ifndef __bpf_htons #define __bpf_htons(x) ((__be16)___constant_swab16((x))) #endif SEC("udp_timestamp_xdp") int xdp_timestamp_func(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; struct iphdr *iph; struct udphdr *udph; __u8 *payload; __u64 timestamp; // 校验以太网头边界 if ((void *)(eth + 1) > data_end) return XDP_PASS; // 判断是否为IP包 if (eth->h_proto != __bpf_htons(ETH_P_IP)) return XDP_PASS; iph = (struct iphdr *)(eth + 1); // 校验IP头边界 if ((void *)(iph + 1) > data_end) return XDP_PASS; // 判断是否为UDP包 if (iph->protocol != IPPROTO_UDP) return XDP_PASS; udph = (struct udphdr *)(iph + 1); payload = (__u8 *)(udph + 1); // 校验payload是否有足够空间存放8字节时间戳 if ((void *)(payload + sizeof(__u64)) > data_end) return XDP_PASS; // 获取时间戳并覆盖payload前8字节 timestamp = bpf_ktime_get_ns(); __builtin_memcpy(payload, ×tamp, sizeof(__u64)); return XDP_PASS; }
4. 额外注意事项
- OVS集成:确保OVS已开启XDP支持,可通过
ovs-vsctl set bridge br0 protocols=OpenFlow13配置,并将XDP程序加载到OVS绑定的物理端口。 - 时间戳一致性:入口和出口的XDP程序统一使用
bpf_ktime_get_ns(系统启动后的纳秒数,全局一致),保证延迟测量的准确性。 - 加载模式选择:CentOS Stream 9支持XDP DRV和SKB模式,使用
xdp-loader load --mode drv ens16f0np0 your_program.o指定DRV模式可获得更好性能。
内容的提问来源于stack exchange,提问作者Emil Ståhl
相关产品推荐
相关产品推荐

