eBPF 是一项革命性的技术,起源于 Linux 内核,它可以在特权上下文中(如操作系统内核)运行沙盒程序。它用于安全有效地扩展内核的功能,而无需通过更改内核源代码或加载内核模块的方式来实现。
eBPF中内核和用户层之间的数据传输常用的框架有两种,分别是perf
和ringbuffer
,前者是从kernel module
而来的,而后者是专门为eBPF
定制的,体验性更好,所有一般都使用后者
在内核层,常规用法为首先使用bpf_ringbuf_reserve
申请一个buffer
,然后调用bpf_ringbuf_submit
提交数据到缓冲区,更详细的可以参考文档https://www.kernel.org/doc/html/next/bpf/ringbuf.html
以下为kprobe使用方式,kprobe
可以简单理解为在内核插桩,目前有两种形式,分别是kprobe
和kretprobe
,前者是在函数开始处插桩,后者则是在函数返回之前插桩,以下为内核层例子
#include "vmlinux.h"
char __license[] SEC("license") = "GPL";
struct file_data {
u32 uid;
u8 filename[256];
};
struct event {
struct file_data file;
};
struct {
__uint(type,BPF_MAP_TYPE_RINGBUF);
__uint(max_entries,1 << 24);
} events SEC(".maps");
const struct event *unused __attribute__((unused));
SEC("kprobe/do_sys_openat2")
int kprobe_openat(struct pt_regs *ctx)
{
u32 uid;
struct event *openat2data;
char *fp = (char *)(ctx->si);
uid = bpf_get_current_uid_gid();
openat2data = bpf_ringbuf_reserve(&events,sizeof(struct event),0);
if(!openat2data)
{
return 0;
}
long res = bpf_probe_read_user_str(&openat2data->file.filename,256,fp);
bpf_printk("uid: %d, filename: %s",uid,openat2data->file.filename);
openat2data->file.uid = uid;
bpf_ringbuf_submit(openat2data,0);
return 0;
}
用户层示例:
package main
import (
"log"
"os"
"os/signal"
"syscall"
"errors"
"bytes"
"encoding/binary"
"fmt"
//"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/rlimit"
"github.com/cilium/ebpf/ringbuf"
"golang.org/x/sys/unix"
)
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -tags "linux" -type event --target=amd64 bpf blog.c -- -I./headers
func main() {
stopper := make(chan os.Signal,1)
signal.Notify(stopper,os.Interrupt,syscall.SIGTERM)
if err := rlimit.RemoveMemlock(); err != nil {
log.Fatal(err);
}
objs := bpfObjects{}
if err := loadBpfObjects(&objs,nil); err != nil {
log.Fatal(err);
}
defer objs.Close()
se, err := link.Kprobe("do_sys_openat2",objs.KprobeOpenat,nil)
if err != nil {
log.Fatal(err)
}
defer se.Close()
rd, err := ringbuf.NewReader(objs.Events)
if err != nil {
log.Fatal(err)
}
defer rd.Close()
go func() {
<-stopper
if err := rd.Close(); err != nil {
log.Fatal(err)
}
}()
log.Println("Waiting for Data")
var event bpfEvent
for {
record, err := rd.Read()
if err != nil {
if errors.Is(err,ringbuf.ErrClosed) {
log.Println("Received signal, exiting...")
return
}
log.Fatal(err)
continue
}
if err := binary.Read(bytes.NewBuffer(record.RawSample),binary.LittleEndian,&event); err != nil {
log.Fatal(err)
continue
}
fmt.Printf("[%+v]: filename -> %s\n",
event.File.Uid,
unix.ByteSliceToString(event.File.Filename[:]))
}
}
效果如下:

Comments 1 条评论