基于Cilium ebpf-go实现网络接口创建追踪的技术求助
实现监控网络接口创建的eBPF方案
一、内核挂载点选择
- 优先选
rtnl_create_link:这个函数是内核处理网络接口创建的核心入口(通过netlink调用),挂载它能精准捕获完整的接口创建事件,比dev_alloc_name更合适——后者仅负责分配接口名称,不对应完整的接口创建流程。 - 内核版本≥5.5时推荐用fentry替代kprobe:fentry是内核提供的稳定追踪入口,无需处理函数参数偏移问题,可靠性比kprobe更高。
二、ebpf-go核心流程拆解
不用纠结示例的复杂细节,核心分三步:
编写eBPF字节码(C语言)
定义perf buffer实现内核态到用户态的信息传递,在追踪程序中提取接口关键信息并发送到buffer:#include <vmlinux.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> struct event { char name[IFNAMSIZ]; int ifindex; }; struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); __uint(key_size, sizeof(u32)); __uint(value_size, sizeof(u32)); } events SEC(".maps"); SEC("fentry/rtnl_create_link") int BPF_PROG(trace_link_create, struct net *net, struct net_device *dev, struct rtnl_link_info *info, struct netlink_ext_ack *extack) { struct event e = {}; bpf_probe_read_str(e.name, sizeof(e.name), dev->name); e.ifindex = dev->ifindex; bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &e, sizeof(e)); return 0; } char _license[] SEC("license") = "GPL";Go用户态程序
加载eBPF程序与maps,创建perf buffer读取器,循环读取并打印事件:package main import ( "fmt" "os" "os/signal" "syscall" "github.com/cilium/ebpf/link" "github.com/cilium/ebpf/perf" "github.com/cilium/ebpf/rlimit" ) // 与eBPF中的event结构体对应 type event struct { Name [16]byte Ifindex int32 } func main() { // 移除内存限制,允许加载eBPF程序 if err := rlimit.RemoveMemlock(); err != nil { fmt.Fprintf(os.Stderr, "移除内存锁失败: %v\n", err) os.Exit(1) } // 加载编译后的eBPF对象 objs := struct { TraceLinkCreate link.Link `ebpf:"trace_link_create"` Events *perf.Map `ebpf:"events"` }{} if err := loadObjs(&objs, nil); err != nil { fmt.Fprintf(os.Stderr, "加载eBPF对象失败: %v\n", err) os.Exit(1) } defer objs.TraceLinkCreate.Close() defer objs.Events.Close() // 创建perf事件读取器 reader, err := perf.NewReader(objs.Events, os.Getpagesize()) if err != nil { fmt.Fprintf(os.Stderr, "创建perf reader失败: %v\n", err) os.Exit(1) } defer reader.Close() // 监听退出信号,实现优雅退出 sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) go func() { <-sig fmt.Println("\n正在退出...") reader.Close() }() fmt.Println("等待网络接口创建事件...") for { record, err := reader.Read() if err != nil { if perf.IsClosed(err) { break } fmt.Fprintf(os.Stderr, "读取perf事件失败: %v\n", err) continue } var e event if err := record.Read(&e); err != nil { fmt.Fprintf(os.Stderr, "解析事件失败: %v\n", err) continue } // 转换为字符串并打印结果 name := string(e.Name[:]) fmt.Printf("新网络接口创建: 索引=%d, 名称=%s\n", e.Ifindex, name) } }编译与运行
- 用
bpf2go工具将C代码编译为Go可加载的对象,需在代码中添加generate指令:
执行编译://go:generate go run github.com/cilium/ebpf/cmd/bpf2go bpf trace_link.c -- -I/path/to/vmlinux-headersgo generate - 以root权限运行Go程序:
sudo go run main.go
- 用
三、关键注意事项
- 内核版本兼容:fentry需要内核≥5.5,若内核版本较低,改用kprobe(需通过
bpftool btf dump file /sys/kernel/btf/vmlinux format c获取内核结构体定义,处理函数参数偏移)。 - 权限要求:必须以root权限运行程序,eBPF程序需要内核态操作权限。
- 读取优化:若
bpf_probe_read_str读取失败,可尝试bpf_core_read_str(需内核≥5.8,依赖BTF)。
内容的提问来源于stack exchange,提问作者Vincenzo Cosi
相关产品推荐
相关产品推荐

