eBPF tracepoint net_dev_queue无法获取正确IP地址求助
问题描述
我编写了一段eBPF代码,用于计算数据包在net_dev_queue与net_dev_xmit两个tracepoint之间的时间差。为生成流键,我需要通过trace_event_raw_net_dev_template结构体提取IP地址,但提取出的IP始终为0.0.0.0。以下是我使用的代码,请问我遗漏了什么?
struct flow_key { __be32 src_ip; __be32 dst_ip; __u16 src_port; __u16 dst_port; __u8 protocol; }; static __always_inline int parse_flow_key(struct sk_buff *skb, struct flow_key *key) { struct iphdr iph; bpf_core_read(&iph, sizeof(iph), skb->head + skb->network_header); if (skb->protocol != bpf_htons(ETH_P_IP)) { // This is not an IP packet bpf_printk("Not an IP packet 0x%xn", skb->protocol); return -1; } key->src_ip = iph.saddr; key->dst_ip = iph.daddr; key->protocol = iph.protocol; bpf_printk(" %pI4 -> %pI4 ", iph.saddr, iph.daddr); // More code } SEC("tracepoint/net/net_dev_queue") int tp_ingress_kernel(struct trace_event_raw_net_dev_template *ctx) { struct sk_buff skb; bpf_probe_read(&skb, sizeof(skb), ctx->skbaddr); struct flow_key key = {}; if (parse_flow_key(&skb, &key) < 0) return 0; bpf_printk("Packet received in kernel\n"); return 0; }
问题分析与解决
你的代码存在三个核心问题,导致无法正确提取IP地址:
错误拷贝整个
struct sk_buffstruct sk_buff是内核中体积庞大的结构体,直接用bpf_probe_read拷贝整个对象不仅低效,还会导致指针类型字段(如head)在eBPF程序中无法指向有效内存,后续读取IP头自然会得到无效值。正确做法是只读取需要的字段,而非整个结构体。IP协议检查顺序颠倒
你先读取IP头,再判断数据包是否为IP类型。如果是非IP包,此时读取的IP头内容完全无效,甚至可能触发非法内存访问。应该先校验协议类型,再读取对应协议的头部数据。直接访问
skb->head指针无效skb->head是内核空间的内存指针,在eBPF程序中不能直接通过skb->head + skb->network_header计算IP头地址,必须使用eBPF提供的安全helper函数读取数据。
修改后的代码示例:
struct flow_key { __be32 src_ip; __be32 dst_ip; __u16 src_port; __u16 dst_port; __u8 protocol; }; static __always_inline int parse_flow_key(struct trace_event_raw_net_dev_template *ctx, struct flow_key *key) { __be16 protocol; // 仅读取skb的protocol字段,无需拷贝整个结构体 bpf_core_read(&protocol, sizeof(protocol), &ctx->skb->protocol); if (protocol != bpf_htons(ETH_P_IP)) { bpf_printk("Not an IP packet 0x%x\n", protocol); return -1; } struct iphdr iph; // 使用bpf_skb_load_bytes安全读取IP头数据 if (bpf_skb_load_bytes(ctx->skb, ctx->skb->network_header, &iph, sizeof(iph)) != 0) { bpf_printk("Failed to read IP header\n"); return -1; } key->src_ip = iph.saddr; key->dst_ip = iph.daddr; key->protocol = iph.protocol; // %pI4需要传入IP地址的指针,而非直接传入整数 bpf_printk(" %pI4 -> %pI4 ", &iph.saddr, &iph.daddr); // 后续端口解析代码... return 0; } SEC("tracepoint/net/net_dev_queue") int tp_ingress_kernel(struct trace_event_raw_net_dev_template *ctx) { struct flow_key key = {}; if (parse_flow_key(ctx, &key) < 0) return 0; bpf_printk("Packet received in kernel\n"); return 0; }
额外注意:
- 访问
ctx->skb的字段时必须用bpf_core_read,因为trace_event_raw_net_dev_template中的skb是内核结构体指针,需要安全读取。 bpf_skb_load_bytes是eBPF专门提供的skb数据读取helper,比直接操作指针更可靠。- 使用
%pI4打印IP地址时,要传入地址指针,不能直接传__be32类型的整数。
内容的提问来源于stack exchange,提问作者Warlock
相关产品推荐
相关产品推荐

