You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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, &timestamp, 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, &timestamp, 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, &timestamp, 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 04:39:51