tc eBPF程序获取PID遇编译警告与函数不可用问题
解决tc/eBPF程序中PID统计的编译与加载错误
编译警告 shift count >= width of type 解决
原因
bpf_get_current_pid_tgid()返回64位__u64类型值(高32位为PID,低32位为TGID),你先将其强制转换为32位int后再右移32位,移位操作超出了32位类型的宽度,触发编译器警告。同时结构体定义中pid字段末尾的逗号属于语法错误。
修复方法
先对64位返回值执行移位操作,再转换为32位无符号整数存储PID,同时修正结构体语法:
// 修正结构体定义 struct packetdets { __u32 source; __u16 source_port; __u32 dest; __u16 dest_port; __u8 ip_protocol; __u32 pid; // 替换末尾逗号为分号 }; // 正确获取PID的代码 __u32 pid = (bpf_get_current_pid_tgid() >> 32) & 0xFFFFFFFF;
加载错误 unknown func bpf_get_current_pid_tgid#14 解决
原因
TC类型的eBPF程序默认未被授权调用进程相关的helper函数bpf_get_current_pid_tgid(),同时需要确保内核版本(≥4.15)和ebpf工具链支持该函数。
修复方法
- 内核版本检查:确认系统内核版本≥4.15(该函数在4.15版本内核中正式引入)。
- Go加载代码配置:使用cilium/ebpf库加载程序时,需要为TC程序添加对该helper函数的许可:
import "github.com/cilium/ebpf" import "log" // 加载程序时添加配置 progSpec, err := loadTcMain() if err != nil { log.Fatalf("加载程序失败: %v", err) } // 方法1:添加到允许列表 progSpec.Programs["tc_main"].AllowlistHelpers = append( progSpec.Programs["tc_main"].AllowlistHelpers, ebpf.FuncBPFGetCurrentPidTgid, ) // 方法2:允许特权helper函数(更简便) progSpec.Programs["tc_main"].Flags |= ebpf.FlagAllowPrivilegedHelpers // 后续加载程序的代码...
- 工具链更新:确保使用的bpftool和clang版本足够新,支持该helper函数的编译。
注意事项
- 仅本地进程发起或接收的数据包能获取到有效PID,转发的数据包无法关联本地进程,PID会返回0或无效值。
- 若内核版本低于4.15,可考虑使用
bpf_get_current_task()结合内核结构体提取PID,但这种方式兼容性差且需要额外权限。
修改后的完整bpf.c代码
#include <linux/bpf.h> #include <linux/pkt_cls.h> #include <linux/if_ether.h> #include "include/ip.h" #include <linux/tcp.h> #include <linux/udp.h> #include "include/helpers.h" #include <linux/sched.h> #include <linux/socket.h> #include <linux/net.h> #include <linux/ip.h> #include <linux/in.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> #define ETH_P_IP 0x0800 /* Internet Protocol Packet */ #define PROTO_TCP 6 #define PROTO_UDP 17 struct packetdets { __u32 source; __u16 source_port; __u32 dest; __u16 dest_port; __u8 ip_protocol; __u32 pid; }; struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, struct packetdets); __type(value, __u64); __uint(max_entries, 1000); } pkt_count SEC(".maps"); SEC("tc_prog") int tc_main(struct __sk_buff *skb) { void *data_end = (void *)(__u64)skb->data_end; void *data = (void *)(__u64)skb->data; struct ethhdr *eth; struct iphdr *ip; struct tcphdr *tcp; struct udphdr *udp; eth = data; if (data + sizeof(struct ethhdr) > data_end) return TC_ACT_OK; ip = data + sizeof(struct ethhdr); if ((void *)ip + sizeof(struct iphdr) > data_end) return TC_ACT_OK; __u32 source = bpf_ntohl(ip->addrs.saddr); __u32 dest = bpf_ntohl(ip->addrs.daddr); if (dest == 3116855193){ return TC_ACT_SHOT; } __u16 source_port; __u16 dest_port; __u8 ip_proto; if (ip->protocol == PROTO_TCP) { tcp = (void *)ip + ip->ihl*4; if ((void *)tcp + sizeof(struct tcphdr) > data_end) { return TC_ACT_OK; } source_port = tcp->source; dest_port = tcp->dest; ip_proto = PROTO_TCP; } else if (ip->protocol == PROTO_UDP) { udp = (void *)ip + ip->ihl*4; if ((void *)udp + sizeof(struct udphdr) > data_end) { return TC_ACT_OK; } source_port = udp->source; dest_port = udp->dest; ip_proto = PROTO_UDP; } else { return TC_ACT_OK; } // 正确获取PID __u32 pid = (bpf_get_current_pid_tgid() >> 32) & 0xFFFFFFFF; struct packetdets key = { .source = source, .source_port = source_port, .dest = dest, .dest_port = dest_port, .ip_protocol = ip_proto, .pid = pid, }; __u64 *count = bpf_map_lookup_elem(&pkt_count, &key); if (count) { __sync_fetch_and_add(count, 1); } else { __u64 value = 1; bpf_map_update_elem(&pkt_count, &key, &value, BPF_ANY); } char hello_str[] = "hello pkt ipv4: %u"; bpf_trace_printk(hello_str, sizeof(hello_str), &skb->remote_ip4); return TC_ACT_OK; } char __license[] SEC("license") = "Dual MIT/GPL";
内容的提问来源于stack exchange,提问作者NobinPegasus
相关产品推荐
相关产品推荐

