跟踪napi_gro_receive_entry时IP头信息缺失的分析
问题描述
在使用eBPF程序跟踪napi_gro_receive_entry
内核跟踪点时,发现获取到的IP头部字段(如saddr
、daddr
、protocol
)为空值。
代码如下:
/* 自定义结构体来映射 napi_gro_receive_entry tracepoint 的 format */
struct napi_gro_receive_entry_data {
unsigned short common_type;
unsigned char common_flags;
unsigned char common_preempt_count;
int common_pid;
/* 以下字段根据 format 来定义 */
char name[4]; // __data_loc char[]
unsigned int napi_id;
unsigned short queue_mapping;
const void *skbaddr; // skbaddr 字段,用来访问 skb 数据
bool vlan_tagged;
unsigned short vlan_proto;
unsigned short vlan_tci;
unsigned short protocol;
unsigned char ip_summed;
unsigned int hash;
bool l4_hash;
unsigned int len;
unsigned int data_len;
unsigned int truesize;
bool mac_header_valid;
int mac_header;
unsigned char nr_frags;
unsigned short gso_size;
unsigned short gso_type;
};
//ip头里面的信息为空
SEC("tp/net/napi_gro_receive_entry")
int trace_napi(struct napi_gro_receive_entry_data *ctx)
{
// 获取当前进程 PID
int pid = bpf_get_current_pid_tgid();
bpf_printk("Hello!!! \n");
// 读取 IP 头中的 protocol 字段
u8 protocol;
struct sk_buff *skb = (struct sk_buff *)ctx->skbaddr;
if(skb == NULL){
bpf_printk("skb is NULL \n");
return 0;
}
struct iphdr *ip = (struct iphdr *)(BPF_CORE_READ(skb, head) +
BPF_CORE_READ(skb, network_header));
if(ip == NULL){
bpf_printk("ip is NULL \n");
return 0;
}
protocol = BPF_CORE_READ(ip, protocol);
// 如果协议字段无效,返回
if (protocol < 0) {
bpf_printk("protocol is NULL \n");
return 0;
}
//输出协议源IP字段
__u32 saddr = BPF_CORE_READ(ip, saddr);
bpf_printk("Source IP: %d.%d.%d.%d\n",
(saddr >> 24) & 0xFF,
(saddr >> 16) & 0xFF,
(saddr >> 8) & 0xFF,
saddr & 0xFF);
//输出协议协议字段
bpf_printk("protocol: %d \n", protocol);
return 0;
}
输出结果如下:
发现读取到的IP头部信息(iphdr结构中的内容)是空的,但是可以确定获取到了skb以及iphdr结构体。
问题分析:(deepseek解答)
-
GRO合并未完成
GRO机制会对多个分片报文进行重组,此时network_header
可能仅指向首个分片的头部位置。当处理后续分片时,skb->network_header
可能尚未更新为有效偏移量。 -
协议头初始化时序
在GRO处理路径中,skb->transport_header
和skb->network_header
的初始化可能延迟到分片重组完成后进行。过早访问会导致读取到内核未初始化的内存区域。 -
SKB共享状态干扰
当skb
被克隆(skb_clone()
)时,多个副本共享数据缓冲区。此时直接访问skb->head + skb->network_header
可能触发内存越界,因为克隆操作的元数据更新存在延迟。
以上给出了deepseek的解释,但是本人并没有验证,后期会去深入源码实现来探讨这个问题。