From c192943339151c416e6aab433543522a295f7cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Thu, 24 Aug 2023 16:33:04 -0300 Subject: [PATCH 01/11] chore: split logic into multiple files This only moves code around, no functional changes. --- buf-common.go | 16 + buf-perf.go | 107 ++++ buf-ring.go | 106 ++++ libbpfgo.go | 1513 +------------------------------------------- link-reader.go | 27 + link.go | 123 ++++ module-iterator.go | 79 +++ module.go | 391 ++++++++++++ prog-common.go | 172 +++++ prog.go | 507 +++++++++++++++ tchook-common.go | 30 + tchook.go | 133 ++++ 12 files changed, 1707 insertions(+), 1497 deletions(-) create mode 100644 buf-common.go create mode 100644 buf-perf.go create mode 100644 buf-ring.go create mode 100644 link-reader.go create mode 100644 link.go create mode 100644 module-iterator.go create mode 100644 module.go create mode 100644 prog-common.go create mode 100644 prog.go create mode 100644 tchook-common.go create mode 100644 tchook.go diff --git a/buf-common.go b/buf-common.go new file mode 100644 index 00000000..1a381aed --- /dev/null +++ b/buf-common.go @@ -0,0 +1,16 @@ +package libbpfgo + +/* +#cgo LDFLAGS: -lelf -lz +#include "libbpfgo.h" +*/ +import "C" + +const ( + // Maximum number of channels (RingBuffers + PerfBuffers) supported + maxEventChannels = 512 +) + +var ( + eventChannels = newRWArray(maxEventChannels) +) diff --git a/buf-perf.go b/buf-perf.go new file mode 100644 index 00000000..7107db03 --- /dev/null +++ b/buf-perf.go @@ -0,0 +1,107 @@ +package libbpfgo + +/* +#cgo LDFLAGS: -lelf -lz +#include "libbpfgo.h" +*/ +import "C" + +import ( + "fmt" + "sync" + "syscall" +) + +// +// PerfBuffer +// + +type PerfBuffer struct { + pb *C.struct_perf_buffer + bpfMap *BPFMap + slot uint + eventsChan chan []byte + lostChan chan uint64 + stop chan struct{} + closed bool + wg sync.WaitGroup +} + +// Poll will wait until timeout in milliseconds to gather +// data from the perf buffer. +func (pb *PerfBuffer) Poll(timeout int) { + pb.stop = make(chan struct{}) + pb.wg.Add(1) + go pb.poll(timeout) +} + +// Deprecated: use PerfBuffer.Poll() instead. +func (pb *PerfBuffer) Start() { + pb.Poll(300) +} + +func (pb *PerfBuffer) Stop() { + if pb.stop != nil { + // Tell the poll goroutine that it's time to exit + close(pb.stop) + + // The event and lost channels should be drained here since the consumer + // may have stopped at this point. Failure to drain it will + // result in a deadlock: the channel will fill up and the poll + // goroutine will block in the callback. + go func() { + // revive:disable:empty-block + for range pb.eventsChan { + } + + if pb.lostChan != nil { + for range pb.lostChan { + } + } + // revive:enable:empty-block + }() + + // Wait for the poll goroutine to exit + pb.wg.Wait() + + // Close the channel -- this is useful for the consumer but + // also to terminate the drain goroutine above. + close(pb.eventsChan) + if pb.lostChan != nil { + close(pb.lostChan) + } + + // This allows Stop() to be called multiple times safely + pb.stop = nil + } +} + +func (pb *PerfBuffer) Close() { + if pb.closed { + return + } + pb.Stop() + C.perf_buffer__free(pb.pb) + eventChannels.remove(pb.slot) + pb.closed = true +} + +// todo: consider writing the perf polling in go as c to go calls (callback) are expensive +func (pb *PerfBuffer) poll(timeout int) error { + defer pb.wg.Done() + + for { + select { + case <-pb.stop: + return nil + default: + err := C.perf_buffer__poll(pb.pb, C.int(timeout)) + if err < 0 { + if syscall.Errno(-err) == syscall.EINTR { + continue + } + return fmt.Errorf("error polling perf buffer: %d", err) + } + } + } +} diff --git a/buf-ring.go b/buf-ring.go new file mode 100644 index 00000000..29d68835 --- /dev/null +++ b/buf-ring.go @@ -0,0 +1,106 @@ +package libbpfgo + +/* +#cgo LDFLAGS: -lelf -lz +#include "libbpfgo.h" +*/ +import "C" + +import ( + "fmt" + "sync" + "syscall" +) + +// +// RingBuffer +// + +type RingBuffer struct { + rb *C.struct_ring_buffer + bpfMap *BPFMap + slot uint + stop chan struct{} + closed bool + wg sync.WaitGroup +} + +// Poll will wait until timeout in milliseconds to gather +// data from the ring buffer. +func (rb *RingBuffer) Poll(timeout int) { + rb.stop = make(chan struct{}) + rb.wg.Add(1) + go rb.poll(timeout) +} + +// Deprecated: use RingBuffer.Poll() instead. +func (rb *RingBuffer) Start() { + rb.Poll(300) +} + +func (rb *RingBuffer) Stop() { + if rb.stop != nil { + // Tell the poll goroutine that it's time to exit + close(rb.stop) + + // The event channel should be drained here since the consumer + // may have stopped at this point. Failure to drain it will + // result in a deadlock: the channel will fill up and the poll + // goroutine will block in the callback. + eventChan := eventChannels.get(rb.slot).(chan []byte) + go func() { + // revive:disable:empty-block + for range eventChan { + } + // revive:enable:empty-block + }() + + // Wait for the poll goroutine to exit + rb.wg.Wait() + + // Close the channel -- this is useful for the consumer but + // also to terminate the drain goroutine above. + close(eventChan) + + // This allows Stop() to be called multiple times safely + rb.stop = nil + } +} + +func (rb *RingBuffer) Close() { + if rb.closed { + return + } + rb.Stop() + C.ring_buffer__free(rb.rb) + eventChannels.remove(rb.slot) + rb.closed = true +} + +func (rb *RingBuffer) isStopped() bool { + select { + case <-rb.stop: + return true + default: + return false + } +} + +func (rb *RingBuffer) poll(timeout int) error { + defer rb.wg.Done() + + for { + err := C.ring_buffer__poll(rb.rb, C.int(timeout)) + if rb.isStopped() { + break + } + + if err < 0 { + if syscall.Errno(-err) == syscall.EINTR { + continue + } + return fmt.Errorf("error polling ring buffer: %d", err) + } + } + return nil +} diff --git a/libbpfgo.go b/libbpfgo.go index 01cdfb96..4e686995 100644 --- a/libbpfgo.go +++ b/libbpfgo.go @@ -7,23 +7,13 @@ package libbpfgo import "C" import ( - "bytes" - "debug/elf" - "encoding/binary" - "errors" "fmt" - "net" - "path/filepath" - "strings" - "sync" "syscall" - "unsafe" ) -const ( - // Maximum number of channels (RingBuffers + PerfBuffers) supported - maxEventChannels = 512 -) +// +// Version +// // MajorVersion returns the major semver version of libbpf. func MajorVersion() int { @@ -41,139 +31,9 @@ func LibbpfVersionString() string { return fmt.Sprintf("v%d.%d", MajorVersion(), MinorVersion()) } -type Module struct { - obj *C.struct_bpf_object - links []*BPFLink - perfBufs []*PerfBuffer - ringBufs []*RingBuffer - elf *elf.File - loaded bool -} - -type BPFProg struct { - name string - prog *C.struct_bpf_program - module *Module - pinnedPath string -} - -type LinkType int - -const ( - Tracepoint LinkType = iota - RawTracepoint - Kprobe - Kretprobe - LSM - PerfEvent - Uprobe - Uretprobe - Tracing - XDP - Cgroup - CgroupLegacy - Netns - Iter -) - -type BPFLinkLegacy struct { - attachType BPFAttachType - cgroupDir string -} - -type BPFLink struct { - link *C.struct_bpf_link - prog *BPFProg - linkType LinkType - eventName string - legacy *BPFLinkLegacy // if set, this is a fake BPFLink -} - -func (l *BPFLink) DestroyLegacy(linkType LinkType) error { - switch l.linkType { - case CgroupLegacy: - return l.prog.DetachCgroupLegacy( - l.legacy.cgroupDir, - l.legacy.attachType, - ) - } - return fmt.Errorf("unable to destroy legacy link") -} - -func (l *BPFLink) Destroy() error { - if l.legacy != nil { - return l.DestroyLegacy(l.linkType) - } - if ret := C.bpf_link__destroy(l.link); ret < 0 { - return syscall.Errno(-ret) - } - l.link = nil - return nil -} - -func (l *BPFLink) FileDescriptor() int { - return int(C.bpf_link__fd(l.link)) -} - -// Deprecated: use BPFLink.FileDescriptor() instead. -func (l *BPFLink) GetFd() int { - return l.FileDescriptor() -} - -func (l *BPFLink) Pin(pinPath string) error { - path := C.CString(pinPath) - errC := C.bpf_link__pin(l.link, path) - C.free(unsafe.Pointer(path)) - if errC != 0 { - return fmt.Errorf("failed to pin link %s to path %s: %w", l.eventName, pinPath, syscall.Errno(-errC)) - } - return nil -} - -func (l *BPFLink) Unpin(pinPath string) error { - path := C.CString(pinPath) - errC := C.bpf_link__unpin(l.link) - C.free(unsafe.Pointer(path)) - if errC != 0 { - return fmt.Errorf("failed to unpin link %s from path %s: %w", l.eventName, pinPath, syscall.Errno(-errC)) - } - return nil -} - -type PerfBuffer struct { - pb *C.struct_perf_buffer - bpfMap *BPFMap - slot uint - eventsChan chan []byte - lostChan chan uint64 - stop chan struct{} - closed bool - wg sync.WaitGroup -} - -type RingBuffer struct { - rb *C.struct_ring_buffer - bpfMap *BPFMap - slot uint - stop chan struct{} - closed bool - wg sync.WaitGroup -} - -type NewModuleArgs struct { - KConfigFilePath string - BTFObjPath string - BPFObjName string - BPFObjPath string - BPFObjBuff []byte - SkipMemlockBump bool -} - -func NewModuleFromFile(bpfObjPath string) (*Module, error) { - return NewModuleFromFileArgs(NewModuleArgs{ - BPFObjPath: bpfObjPath, - }) -} +// +// Strict Mode +// // LibbpfStrictMode is an enum as defined in https://github.com/libbpf/libbpf/blob/2cd2d03f63242c048a896179398c68d2dbefe3d6/src/libbpf_legacy.h#L23 type LibbpfStrictMode uint32 @@ -208,1375 +68,34 @@ func (b LibbpfStrictMode) String() (str string) { return str } -// NOTE: libbpf has started raising limits by default but, unfortunately, that -// seems to be failing in current libbpf version. The memory limit bump might be -// removed once this is sorted out. -func bumpMemlockRlimit() error { - var rLimit syscall.Rlimit - rLimit.Max = 512 << 20 /* 512 MBs */ - rLimit.Cur = 512 << 20 /* 512 MBs */ - err := syscall.Setrlimit(C.RLIMIT_MEMLOCK, &rLimit) - if err != nil { - return fmt.Errorf("error setting rlimit: %v", err) - } - return nil -} - func SetStrictMode(mode LibbpfStrictMode) { C.libbpf_set_strict_mode(uint32(mode)) } -func NewModuleFromFileArgs(args NewModuleArgs) (*Module, error) { - f, err := elf.Open(args.BPFObjPath) - if err != nil { - return nil, err - } - C.cgo_libbpf_set_print_fn() - - // If skipped, we rely on libbpf to do the bumping if deemed necessary - if !args.SkipMemlockBump { - // TODO: remove this once libbpf memory limit bump issue is solved - if err := bumpMemlockRlimit(); err != nil { - return nil, err - } - } - - opts := C.struct_bpf_object_open_opts{} - opts.sz = C.sizeof_struct_bpf_object_open_opts - - bpfFile := C.CString(args.BPFObjPath) - defer C.free(unsafe.Pointer(bpfFile)) - - // instruct libbpf to use user provided kernel BTF file - if args.BTFObjPath != "" { - btfFile := C.CString(args.BTFObjPath) - opts.btf_custom_path = btfFile - defer C.free(unsafe.Pointer(btfFile)) - } - - // instruct libbpf to use user provided KConfigFile - if args.KConfigFilePath != "" { - kConfigFile := C.CString(args.KConfigFilePath) - opts.kconfig = kConfigFile - defer C.free(unsafe.Pointer(kConfigFile)) - } - - obj, errno := C.bpf_object__open_file(bpfFile, &opts) - if obj == nil { - return nil, fmt.Errorf("failed to open BPF object at path %s: %w", args.BPFObjPath, errno) - } - - return &Module{ - obj: obj, - elf: f, - }, nil -} - -func NewModuleFromBuffer(bpfObjBuff []byte, bpfObjName string) (*Module, error) { - return NewModuleFromBufferArgs(NewModuleArgs{ - BPFObjBuff: bpfObjBuff, - BPFObjName: bpfObjName, - }) -} - -func NewModuleFromBufferArgs(args NewModuleArgs) (*Module, error) { - f, err := elf.NewFile(bytes.NewReader(args.BPFObjBuff)) - if err != nil { - return nil, err - } - C.cgo_libbpf_set_print_fn() - - // TODO: remove this once libbpf memory limit bump issue is solved - if err := bumpMemlockRlimit(); err != nil { - return nil, err - } - - if args.BTFObjPath == "" { - args.BTFObjPath = "/sys/kernel/btf/vmlinux" - } - - cBTFFilePath := C.CString(args.BTFObjPath) - defer C.free(unsafe.Pointer(cBTFFilePath)) - cKconfigPath := C.CString(args.KConfigFilePath) - defer C.free(unsafe.Pointer(cKconfigPath)) - cBPFObjName := C.CString(args.BPFObjName) - defer C.free(unsafe.Pointer(cBPFObjName)) - cBPFBuff := unsafe.Pointer(C.CBytes(args.BPFObjBuff)) - defer C.free(cBPFBuff) - cBPFBuffSize := C.size_t(len(args.BPFObjBuff)) - - if len(args.KConfigFilePath) <= 2 { - cKconfigPath = nil - } - - cOpts, errno := C.cgo_bpf_object_open_opts_new(cBTFFilePath, cKconfigPath, cBPFObjName) - if cOpts == nil { - return nil, fmt.Errorf("failed to create bpf_object_open_opts to %s: %w", args.BPFObjName, errno) - } - defer C.cgo_bpf_object_open_opts_free(cOpts) - - obj, errno := C.bpf_object__open_mem(cBPFBuff, cBPFBuffSize, cOpts) - if obj == nil { - return nil, fmt.Errorf("failed to open BPF object %s: %w", args.BPFObjName, errno) - } - - return &Module{ - obj: obj, - elf: f, - }, nil -} - -func (m *Module) Close() { - for _, pb := range m.perfBufs { - pb.Close() - } - for _, rb := range m.ringBufs { - rb.Close() - } - for _, link := range m.links { - if link.link != nil { - link.Destroy() - } - } - C.bpf_object__close(m.obj) -} - -func (m *Module) BPFLoadObject() error { - ret := C.bpf_object__load(m.obj) - if ret != 0 { - return fmt.Errorf("failed to load BPF object: %w", syscall.Errno(-ret)) - } - m.loaded = true - m.elf.Close() - - return nil -} - -// InitGlobalVariable sets global variables (defined in .data or .rodata) -// in bpf code. It must be called before the BPF object is loaded. -func (m *Module) InitGlobalVariable(name string, value interface{}) error { - if m.loaded { - return errors.New("must be called before the BPF object is loaded") - } - s, err := getGlobalVariableSymbol(m.elf, name) - if err != nil { - return err - } - bpfMap, err := m.GetMap(s.sectionName) - if err != nil { - return err - } - - // get current value - currMapValue, err := bpfMap.InitialValue() - if err != nil { - return err - } - - // generate new value - newMapValue := make([]byte, bpfMap.ValueSize()) - copy(newMapValue, currMapValue) - data := bytes.NewBuffer(nil) - if err := binary.Write(data, s.byteOrder, value); err != nil { - return err - } - varValue := data.Bytes() - start := s.offset - end := s.offset + len(varValue) - if len(varValue) > s.size || end > bpfMap.ValueSize() { - return errors.New("invalid value") - } - copy(newMapValue[start:end], varValue) - - // save new value - err = bpfMap.SetInitialValue(unsafe.Pointer(&newMapValue[0])) - return err -} - -func (m *Module) GetMap(mapName string) (*BPFMap, error) { - cs := C.CString(mapName) - bpfMapC, errno := C.bpf_object__find_map_by_name(m.obj, cs) - C.free(unsafe.Pointer(cs)) - if bpfMapC == nil { - return nil, fmt.Errorf("failed to find BPF map %s: %w", mapName, errno) - } - - bpfMap := &BPFMap{ - bpfMap: bpfMapC, - module: m, - } - - if !m.loaded { - bpfMap.bpfMapLow = &BPFMapLow{ - fd: -1, - info: &BPFMapInfo{}, - } - - return bpfMap, nil - } - - fd := bpfMap.FileDescriptor() - info, err := GetMapInfoByFD(fd) - if err != nil { - // The consumer of this API may not have sufficient privileges to get - // map info via BPF syscall. However, "some" map info still can be - // retrieved from the BPF object itself. - bpfMap.bpfMapLow = &BPFMapLow{ - fd: fd, - info: &BPFMapInfo{ - Type: bpfMap.Type(), - ID: 0, - KeySize: uint32(bpfMap.KeySize()), - ValueSize: uint32(bpfMap.ValueSize()), - MaxEntries: bpfMap.MaxEntries(), - MapFlags: uint32(bpfMap.MapFlags()), - Name: bpfMap.Name(), - IfIndex: bpfMap.IfIndex(), - BTFVmlinuxValueTypeID: 0, - NetnsDev: 0, - NetnsIno: 0, - BTFID: 0, - BTFKeyTypeID: 0, - BTFValueTypeID: 0, - MapExtra: bpfMap.MapExtra(), - }, - } - - return bpfMap, nil - } - - bpfMap.bpfMapLow = &BPFMapLow{ - fd: fd, - info: info, - } - - return bpfMap, nil -} - -// BPFObjectProgramIterator iterates over maps in a BPF object -type BPFObjectIterator struct { - m *Module - prevProg *BPFProg - prevMap *BPFMap -} - -func (m *Module) Iterator() *BPFObjectIterator { - return &BPFObjectIterator{ - m: m, - prevProg: nil, - prevMap: nil, - } -} - -func (it *BPFObjectIterator) NextMap() *BPFMap { - var startMap *C.struct_bpf_map - if it.prevMap != nil && it.prevMap.bpfMap != nil { - startMap = it.prevMap.bpfMap - } - - m := C.bpf_object__next_map(it.m.obj, startMap) - if m == nil { - return nil - } - - bpfMap := &BPFMap{ - bpfMap: m, - module: it.m, - } - it.prevMap = bpfMap - - if !bpfMap.module.loaded { - bpfMap.bpfMapLow = &BPFMapLow{ - fd: -1, - info: &BPFMapInfo{}, - } - - return bpfMap - } - - fd := bpfMap.FileDescriptor() - info, err := GetMapInfoByFD(fd) - if err != nil { - return nil - } - - bpfMap.bpfMapLow = &BPFMapLow{ - fd: fd, - info: info, - } - - return bpfMap -} - -func (it *BPFObjectIterator) NextProgram() *BPFProg { - var startProg *C.struct_bpf_program - if it.prevProg != nil && it.prevProg.prog != nil { - startProg = it.prevProg.prog - } - - p := C.bpf_object__next_program(it.m.obj, startProg) - if p == nil { - return nil - } - cName := C.bpf_program__name(p) - - prog := &BPFProg{ - name: C.GoString(cName), - prog: p, - module: it.m, - } - it.prevProg = prog - return prog -} - -// BPFLinkReader read data from a BPF link -type BPFLinkReader struct { - l *BPFLink - fd int -} - -func (l *BPFLink) Reader() (*BPFLinkReader, error) { - fd, errno := C.bpf_iter_create(C.int(l.FileDescriptor())) - if fd < 0 { - return nil, fmt.Errorf("failed to create reader: %w", errno) - } - return &BPFLinkReader{ - l: l, - fd: int(uintptr(fd)), - }, nil -} - -func (i *BPFLinkReader) Read(p []byte) (n int, err error) { - return syscall.Read(i.fd, p) -} - -func (i *BPFLinkReader) Close() error { - return syscall.Close(i.fd) -} - -func (m *Module) GetProgram(progName string) (*BPFProg, error) { - cs := C.CString(progName) - prog, errno := C.bpf_object__find_program_by_name(m.obj, cs) - C.free(unsafe.Pointer(cs)) - if prog == nil { - return nil, fmt.Errorf("failed to find BPF program %s: %w", progName, errno) - } - - return &BPFProg{ - name: progName, - prog: prog, - module: m, - }, nil -} - -func (p *BPFProg) FileDescriptor() int { - return int(C.bpf_program__fd(p.prog)) -} - -// Deprecated: use BPFProg.FileDescriptor() instead. -func (p *BPFProg) GetFd() int { - return p.FileDescriptor() -} - -func (p *BPFProg) Pin(path string) error { - absPath, err := filepath.Abs(path) - if err != nil { - return fmt.Errorf("invalid path: %s: %v", path, err) - } - - cs := C.CString(absPath) - ret := C.bpf_program__pin(p.prog, cs) - C.free(unsafe.Pointer(cs)) - if ret != 0 { - return fmt.Errorf("failed to pin program %s to %s: %w", p.name, path, syscall.Errno(-ret)) - } - p.pinnedPath = absPath - return nil -} - -func (p *BPFProg) Unpin(path string) error { - cs := C.CString(path) - ret := C.bpf_program__unpin(p.prog, cs) - C.free(unsafe.Pointer(cs)) - if ret != 0 { - return fmt.Errorf("failed to unpin program %s to %s: %w", p.name, path, syscall.Errno(-ret)) - } - p.pinnedPath = "" - return nil -} - -func (p *BPFProg) GetModule() *Module { - return p.module -} - -func (p *BPFProg) Name() string { - return C.GoString(C.bpf_program__name(p.prog)) -} - -// Deprecated: use BPFProg.Name() instead. -func (p *BPFProg) GetName() string { - return p.Name() -} - -func (p *BPFProg) SectionName() string { - return C.GoString(C.bpf_program__section_name(p.prog)) -} - -// Deprecated: use BPFProg.SectionName() instead. -func (p *BPFProg) GetSectionName() string { - return p.SectionName() -} - -func (p *BPFProg) PinPath() string { - return p.pinnedPath // There's no LIBBPF_API for bpf program -} - -// Deprecated: use BPFProg.PinPath() instead. -func (p *BPFProg) GetPinPath() string { - return p.PinPath() -} - -// BPFProgType is an enum as defined in https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/bpf.h -type BPFProgType uint32 - -const ( - BPFProgTypeUnspec BPFProgType = iota - BPFProgTypeSocketFilter - BPFProgTypeKprobe - BPFProgTypeSchedCls - BPFProgTypeSchedAct - BPFProgTypeTracepoint - BPFProgTypeXdp - BPFProgTypePerfEvent - BPFProgTypeCgroupSkb - BPFProgTypeCgroupSock - BPFProgTypeLwtIn - BPFProgTypeLwtOut - BPFProgTypeLwtXmit - BPFProgTypeSockOps - BPFProgTypeSkSkb - BPFProgTypeCgroupDevice - BPFProgTypeSkMsg - BPFProgTypeRawTracepoint - BPFProgTypeCgroupSockAddr - BPFProgTypeLwtSeg6Local - BPFProgTypeLircMode2 - BPFProgTypeSkReuseport - BPFProgTypeFlowDissector - BPFProgTypeCgroupSysctl - BPFProgTypeRawTracepointWritable - BPFProgTypeCgroupSockopt - BPFProgTypeTracing - BPFProgTypeStructOps - BPFProgTypeExt - BPFProgTypeLsm - BPFProgTypeSkLookup - BPFProgTypeSyscall -) - -func (b BPFProgType) Value() uint64 { return uint64(b) } - -func (b BPFProgType) String() (str string) { - x := map[BPFProgType]string{ - BPFProgTypeUnspec: "BPF_PROG_TYPE_UNSPEC", - BPFProgTypeSocketFilter: "BPF_PROG_TYPE_SOCKET_FILTER", - BPFProgTypeKprobe: "BPF_PROG_TYPE_KPROBE", - BPFProgTypeSchedCls: "BPF_PROG_TYPE_SCHED_CLS", - BPFProgTypeSchedAct: "BPF_PROG_TYPE_SCHED_ACT", - BPFProgTypeTracepoint: "BPF_PROG_TYPE_TRACEPOINT", - BPFProgTypeXdp: "BPF_PROG_TYPE_XDP", - BPFProgTypePerfEvent: "BPF_PROG_TYPE_PERF_EVENT", - BPFProgTypeCgroupSkb: "BPF_PROG_TYPE_CGROUP_SKB", - BPFProgTypeCgroupSock: "BPF_PROG_TYPE_CGROUP_SOCK", - BPFProgTypeLwtIn: "BPF_PROG_TYPE_LWT_IN", - BPFProgTypeLwtOut: "BPF_PROG_TYPE_LWT_OUT", - BPFProgTypeLwtXmit: "BPF_PROG_TYPE_LWT_XMIT", - BPFProgTypeSockOps: "BPF_PROG_TYPE_SOCK_OPS", - BPFProgTypeSkSkb: "BPF_PROG_TYPE_SK_SKB", - BPFProgTypeCgroupDevice: "BPF_PROG_TYPE_CGROUP_DEVICE", - BPFProgTypeSkMsg: "BPF_PROG_TYPE_SK_MSG", - BPFProgTypeRawTracepoint: "BPF_PROG_TYPE_RAW_TRACEPOINT", - BPFProgTypeCgroupSockAddr: "BPF_PROG_TYPE_CGROUP_SOCK_ADDR", - BPFProgTypeLwtSeg6Local: "BPF_PROG_TYPE_LWT_SEG6LOCAL", - BPFProgTypeLircMode2: "BPF_PROG_TYPE_LIRC_MODE2", - BPFProgTypeSkReuseport: "BPF_PROG_TYPE_SK_REUSEPORT", - BPFProgTypeFlowDissector: "BPF_PROG_TYPE_FLOW_DISSECTOR", - BPFProgTypeCgroupSysctl: "BPF_PROG_TYPE_CGROUP_SYSCTL", - BPFProgTypeRawTracepointWritable: "BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE", - BPFProgTypeCgroupSockopt: "BPF_PROG_TYPE_CGROUP_SOCKOPT", - BPFProgTypeTracing: "BPF_PROG_TYPE_TRACING", - BPFProgTypeStructOps: "BPF_PROG_TYPE_STRUCT_OPS", - BPFProgTypeExt: "BPF_PROG_TYPE_EXT", - BPFProgTypeLsm: "BPF_PROG_TYPE_LSM", - BPFProgTypeSkLookup: "BPF_PROG_TYPE_SK_LOOKUP", - BPFProgTypeSyscall: "BPF_PROG_TYPE_SYSCALL", - } - str = x[b] - if str == "" { - str = BPFProgTypeUnspec.String() - } - return str -} - -type BPFAttachType uint32 - -const ( - BPFAttachTypeCgroupInetIngress BPFAttachType = iota - BPFAttachTypeCgroupInetEgress - BPFAttachTypeCgroupInetSockCreate - BPFAttachTypeCgroupSockOps - BPFAttachTypeSKSKBStreamParser - BPFAttachTypeSKSKBStreamVerdict - BPFAttachTypeCgroupDevice - BPFAttachTypeSKMSGVerdict - BPFAttachTypeCgroupInet4Bind - BPFAttachTypeCgroupInet6Bind - BPFAttachTypeCgroupInet4Connect - BPFAttachTypeCgroupInet6Connect - BPFAttachTypeCgroupInet4PostBind - BPFAttachTypeCgroupInet6PostBind - BPFAttachTypeCgroupUDP4SendMsg - BPFAttachTypeCgroupUDP6SendMsg - BPFAttachTypeLircMode2 - BPFAttachTypeFlowDissector - BPFAttachTypeCgroupSysctl - BPFAttachTypeCgroupUDP4RecvMsg - BPFAttachTypeCgroupUDP6RecvMsg - BPFAttachTypeCgroupGetSockOpt - BPFAttachTypeCgroupSetSockOpt - BPFAttachTypeTraceRawTP - BPFAttachTypeTraceFentry - BPFAttachTypeTraceFexit - BPFAttachTypeModifyReturn - BPFAttachTypeLSMMac - BPFAttachTypeTraceIter - BPFAttachTypeCgroupInet4GetPeerName - BPFAttachTypeCgroupInet6GetPeerName - BPFAttachTypeCgroupInet4GetSockName - BPFAttachTypeCgroupInet6GetSockName - BPFAttachTypeXDPDevMap - BPFAttachTypeCgroupInetSockRelease - BPFAttachTypeXDPCPUMap - BPFAttachTypeSKLookup - BPFAttachTypeXDP - BPFAttachTypeSKSKBVerdict - BPFAttachTypeSKReusePortSelect - BPFAttachTypeSKReusePortSelectorMigrate - BPFAttachTypePerfEvent - BPFAttachTypeTraceKprobeMulti -) - -func (p *BPFProg) GetType() BPFProgType { - return BPFProgType(C.bpf_program__type(p.prog)) -} - -func (p *BPFProg) SetAutoload(autoload bool) error { - cbool := C.bool(autoload) - ret := C.bpf_program__set_autoload(p.prog, cbool) - if ret != 0 { - return fmt.Errorf("failed to set bpf program autoload: %w", syscall.Errno(-ret)) - } - return nil -} - -// AttachGeneric is used to attach the BPF program using autodetection -// for the attach target. You can specify the destination in BPF code -// via the SEC() such as `SEC("fentry/some_kernel_func")` -func (p *BPFProg) AttachGeneric() (*BPFLink, error) { - link, errno := C.bpf_program__attach(p.prog) - if link == nil { - return nil, fmt.Errorf("failed to attach program: %w", errno) - } - bpfLink := &BPFLink{ - link: link, - prog: p, - linkType: Tracing, - eventName: fmt.Sprintf("tracing-%s", p.name), - } - return bpfLink, nil -} - -// SetAttachTarget can be used to specify the program and/or function to attach -// the BPF program to. To attach to a kernel function specify attachProgFD as 0 -func (p *BPFProg) SetAttachTarget(attachProgFD int, attachFuncName string) error { - cs := C.CString(attachFuncName) - ret := C.bpf_program__set_attach_target(p.prog, C.int(attachProgFD), cs) - C.free(unsafe.Pointer(cs)) - if ret != 0 { - return fmt.Errorf("failed to set attach target for program %s %s %w", p.name, attachFuncName, syscall.Errno(-ret)) - } - return nil -} - -func (p *BPFProg) SetProgramType(progType BPFProgType) { - C.bpf_program__set_type(p.prog, C.enum_bpf_prog_type(int(progType))) -} - -func (p *BPFProg) SetAttachType(attachType BPFAttachType) { - C.bpf_program__set_expected_attach_type(p.prog, C.enum_bpf_attach_type(int(attachType))) -} - -// getCgroupDirFD returns a file descriptor for a given cgroup2 directory path -func getCgroupDirFD(cgroupV2DirPath string) (int, error) { - // revive:disable - const ( - O_DIRECTORY int = syscall.O_DIRECTORY - O_RDONLY int = syscall.O_RDONLY - ) - // revive:enable - fd, err := syscall.Open(cgroupV2DirPath, O_DIRECTORY|O_RDONLY, 0) - if fd < 0 { - return 0, fmt.Errorf("failed to open cgroupv2 directory path %s: %w", cgroupV2DirPath, err) - } - return fd, nil -} - -// AttachCgroup attaches the BPFProg to a cgroup described by given fd. -func (p *BPFProg) AttachCgroup(cgroupV2DirPath string) (*BPFLink, error) { - cgroupDirFD, err := getCgroupDirFD(cgroupV2DirPath) - if err != nil { - return nil, err - } - defer syscall.Close(cgroupDirFD) - - link, errno := C.bpf_program__attach_cgroup(p.prog, C.int(cgroupDirFD)) - if link == nil { - return nil, fmt.Errorf("failed to attach cgroup on cgroupv2 %s to program %s: %w", cgroupV2DirPath, p.name, errno) - } - - // dirName will be used in bpfLink.eventName. eventName follows a format - // convention and is used to better identify link types and what they are - // linked with in case of errors or similar needs. Having eventName as: - // cgroup-progName-/sys/fs/cgroup/unified/ would look weird so replace it - // to be cgroup-progName-sys-fs-cgroup-unified instead. - dirName := strings.ReplaceAll(cgroupV2DirPath[1:], "/", "-") - bpfLink := &BPFLink{ - link: link, - prog: p, - linkType: Cgroup, - eventName: fmt.Sprintf("cgroup-%s-%s", p.name, dirName), - } - p.module.links = append(p.module.links, bpfLink) - return bpfLink, nil -} - -// AttachCgroupLegacy attaches the BPFProg to a cgroup described by the given -// fd. It first tries to use the most recent attachment method and, if that does -// not work, instead of failing, it tries the legacy way: to attach the cgroup -// eBPF program without previously creating a link. This allows attaching cgroup -// eBPF ingress/egress in older kernels. Note: the first attempt error message -// is filtered out inside libbpf_print_fn() as it is actually a feature probe -// attempt as well. // -// Related kernel commit: https://github.com/torvalds/linux/commit/af6eea57437a -func (p *BPFProg) AttachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttachType) (*BPFLink, error) { - bpfLink, err := p.AttachCgroup(cgroupV2DirPath) - if err == nil { - return bpfLink, nil - } - // Try the legacy attachment method before fully failing - cgroupDirFD, err := getCgroupDirFD(cgroupV2DirPath) - if err != nil { - return nil, err - } - defer syscall.Close(cgroupDirFD) - progFD := C.bpf_program__fd(p.prog) - ret := C.cgo_bpf_prog_attach_cgroup_legacy(progFD, C.int(cgroupDirFD), C.int(attachType)) - if ret < 0 { - return nil, fmt.Errorf("failed to attach (legacy) program %s to cgroupv2 %s", p.name, cgroupV2DirPath) - } - dirName := strings.ReplaceAll(cgroupV2DirPath[1:], "/", "-") - - bpfLinkLegacy := &BPFLinkLegacy{ - attachType: attachType, - cgroupDir: cgroupV2DirPath, - } - fakeBpfLink := &BPFLink{ - link: nil, // detach/destroy made with progfd - prog: p, - eventName: fmt.Sprintf("cgroup-%s-%s", p.name, dirName), - // info bellow needed for detach (there isn't a real ebpf link) - linkType: CgroupLegacy, - legacy: bpfLinkLegacy, - } - return fakeBpfLink, nil -} - -// DetachCgroupLegacy detaches the BPFProg from a cgroup described by the given -// fd. This is needed because in legacy attachment there is no BPFLink, just a -// fake one (kernel did not support it, nor libbpf). This function should be -// called by the (*BPFLink)->Destroy() function, since BPFLink is emulated (so -// users donĀ“t need to distinguish between regular and legacy cgroup -// detachments). -func (p *BPFProg) DetachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttachType) error { - cgroupDirFD, err := getCgroupDirFD(cgroupV2DirPath) - if err != nil { - return err - } - defer syscall.Close(cgroupDirFD) - progFD := C.bpf_program__fd(p.prog) - ret := C.cgo_bpf_prog_detach_cgroup_legacy(progFD, C.int(cgroupDirFD), C.int(attachType)) - if ret < 0 { - return fmt.Errorf("failed to detach (legacy) program %s from cgroupv2 %s", p.name, cgroupV2DirPath) - } - return nil -} - -func (p *BPFProg) AttachXDP(deviceName string) (*BPFLink, error) { - iface, err := net.InterfaceByName(deviceName) - if err != nil { - return nil, fmt.Errorf("failed to find device by name %s: %w", deviceName, err) - } - link, errno := C.bpf_program__attach_xdp(p.prog, C.int(iface.Index)) - if link == nil { - return nil, fmt.Errorf("failed to attach xdp on device %s to program %s: %w", deviceName, p.name, errno) - } - - bpfLink := &BPFLink{ - link: link, - prog: p, - linkType: XDP, - eventName: fmt.Sprintf("xdp-%s-%s", p.name, deviceName), - } - p.module.links = append(p.module.links, bpfLink) - return bpfLink, nil -} - -func (p *BPFProg) AttachTracepoint(category, name string) (*BPFLink, error) { - tpCategory := C.CString(category) - tpName := C.CString(name) - link, errno := C.bpf_program__attach_tracepoint(p.prog, tpCategory, tpName) - C.free(unsafe.Pointer(tpCategory)) - C.free(unsafe.Pointer(tpName)) - if link == nil { - return nil, fmt.Errorf("failed to attach tracepoint %s to program %s: %w", name, p.name, errno) - } - - bpfLink := &BPFLink{ - link: link, - prog: p, - linkType: Tracepoint, - eventName: name, - } - p.module.links = append(p.module.links, bpfLink) - return bpfLink, nil -} - -func (p *BPFProg) AttachRawTracepoint(tpEvent string) (*BPFLink, error) { - cs := C.CString(tpEvent) - link, errno := C.bpf_program__attach_raw_tracepoint(p.prog, cs) - C.free(unsafe.Pointer(cs)) - if link == nil { - return nil, fmt.Errorf("failed to attach raw tracepoint %s to program %s: %w", tpEvent, p.name, errno) - } - - bpfLink := &BPFLink{ - link: link, - prog: p, - linkType: RawTracepoint, - eventName: tpEvent, - } - p.module.links = append(p.module.links, bpfLink) - return bpfLink, nil -} - -func (p *BPFProg) AttachLSM() (*BPFLink, error) { - link, errno := C.bpf_program__attach_lsm(p.prog) - if link == nil { - return nil, fmt.Errorf("failed to attach lsm to program %s: %w", p.name, errno) - } - - bpfLink := &BPFLink{ - link: link, - prog: p, - linkType: LSM, - } - p.module.links = append(p.module.links, bpfLink) - return bpfLink, nil -} - -func (p *BPFProg) AttachPerfEvent(fd int) (*BPFLink, error) { - link, errno := C.bpf_program__attach_perf_event(p.prog, C.int(fd)) - if link == nil { - return nil, fmt.Errorf("failed to attach perf event to program %s: %w", p.name, errno) - } - - bpfLink := &BPFLink{ - link: link, - prog: p, - linkType: PerfEvent, - } - p.module.links = append(p.module.links, bpfLink) - return bpfLink, nil -} - -// this API should be used for kernels > 4.17 -func (p *BPFProg) AttachKprobe(kp string) (*BPFLink, error) { - return doAttachKprobe(p, kp, false) -} - -// this API should be used for kernels > 4.17 -func (p *BPFProg) AttachKretprobe(kp string) (*BPFLink, error) { - return doAttachKprobe(p, kp, true) -} - -func (p *BPFProg) AttachNetns(networkNamespacePath string) (*BPFLink, error) { - fd, err := syscall.Open(networkNamespacePath, syscall.O_RDONLY, 0) - if fd < 0 { - return nil, fmt.Errorf("failed to open network namespace path %s: %w", networkNamespacePath, err) - } - link, errno := C.bpf_program__attach_netns(p.prog, C.int(fd)) - if link == nil { - return nil, fmt.Errorf("failed to attach network namespace on %s to program %s: %w", networkNamespacePath, p.name, errno) - } - - // fileName will be used in bpfLink.eventName. eventName follows a format - // convention and is used to better identify link types and what they are - // linked with in case of errors or similar needs. Having eventName as: - // netns-progName-/proc/self/ns/net would look weird so replace it - // to be netns-progName-proc-self-ns-net instead. - fileName := strings.ReplaceAll(networkNamespacePath[1:], "/", "-") - bpfLink := &BPFLink{ - link: link, - prog: p, - linkType: Netns, - eventName: fmt.Sprintf("netns-%s-%s", p.name, fileName), - } - p.module.links = append(p.module.links, bpfLink) - return bpfLink, nil -} - -type BPFCgroupIterOrder uint32 - -const ( - BPFIterOrderUnspec BPFCgroupIterOrder = iota - BPFIterSelfOnly - BPFIterDescendantsPre - BPFIterDescendantsPost - BPFIterAncestorsUp -) - -type IterOpts struct { - MapFd int - CgroupIterOrder BPFCgroupIterOrder - CgroupFd int - CgroupId uint64 - Tid int - Pid int - PidFd int -} - -func (p *BPFProg) AttachIter(opts IterOpts) (*BPFLink, error) { - mapFd := C.uint(opts.MapFd) - cgroupIterOrder := uint32(opts.CgroupIterOrder) - cgroupFd := C.uint(opts.CgroupFd) - cgroupId := C.ulonglong(opts.CgroupId) - tid := C.uint(opts.Tid) - pid := C.uint(opts.Pid) - pidFd := C.uint(opts.PidFd) - cOpts, errno := C.cgo_bpf_iter_attach_opts_new(mapFd, cgroupIterOrder, cgroupFd, cgroupId, tid, pid, pidFd) - if cOpts == nil { - return nil, fmt.Errorf("failed to create iter_attach_opts to program %s: %w", p.name, errno) - } - defer C.cgo_bpf_iter_attach_opts_free(cOpts) - - link, errno := C.bpf_program__attach_iter(p.prog, cOpts) - if link == nil { - return nil, fmt.Errorf("failed to attach iter to program %s: %w", p.name, errno) - } - eventName := fmt.Sprintf("iter-%s-%d", p.name, opts.MapFd) - bpfLink := &BPFLink{ - link: link, - prog: p, - linkType: Iter, - eventName: eventName, - } - p.module.links = append(p.module.links, bpfLink) - return bpfLink, nil -} - -func doAttachKprobe(prog *BPFProg, kp string, isKretprobe bool) (*BPFLink, error) { - cs := C.CString(kp) - cbool := C.bool(isKretprobe) - link, errno := C.bpf_program__attach_kprobe(prog.prog, cbool, cs) - C.free(unsafe.Pointer(cs)) - if link == nil { - return nil, fmt.Errorf("failed to attach %s k(ret)probe to program %s: %w", kp, prog.name, errno) - } - - kpType := Kprobe - if isKretprobe { - kpType = Kretprobe - } - - bpfLink := &BPFLink{ - link: link, - prog: prog, - linkType: kpType, - eventName: kp, - } - prog.module.links = append(prog.module.links, bpfLink) - return bpfLink, nil -} - -// AttachUprobe attaches the BPFProgram to entry of the symbol in the library or binary at 'path' -// which can be relative or absolute. A pid can be provided to attach to, or -1 can be specified -// to attach to all processes -func (p *BPFProg) AttachUprobe(pid int, path string, offset uint32) (*BPFLink, error) { - absPath, err := filepath.Abs(path) - if err != nil { - return nil, err - } - - return doAttachUprobe(p, false, pid, absPath, offset) -} - -// AttachURetprobe attaches the BPFProgram to exit of the symbol in the library or binary at 'path' -// which can be relative or absolute. A pid can be provided to attach to, or -1 can be specified -// to attach to all processes -func (p *BPFProg) AttachURetprobe(pid int, path string, offset uint32) (*BPFLink, error) { - absPath, err := filepath.Abs(path) - if err != nil { - return nil, err - } - - return doAttachUprobe(p, true, pid, absPath, offset) -} - -func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offset uint32) (*BPFLink, error) { - retCBool := C.bool(isUretprobe) - pidCint := C.int(pid) - pathCString := C.CString(path) - offsetCsizet := C.size_t(offset) - - link, errno := C.bpf_program__attach_uprobe(prog.prog, retCBool, pidCint, pathCString, offsetCsizet) - C.free(unsafe.Pointer(pathCString)) - if link == nil { - return nil, fmt.Errorf("failed to attach u(ret)probe to program %s:%d with pid %d: %w ", path, offset, pid, errno) - } - - upType := Uprobe - if isUretprobe { - upType = Uretprobe - } - - bpfLink := &BPFLink{ - link: link, - prog: prog, - linkType: upType, - eventName: fmt.Sprintf("%s:%d:%d", path, pid, offset), - } - return bpfLink, nil -} - -type AttachFlag uint32 - -const ( - BPFFNone AttachFlag = 0 - BPFFAllowOverride AttachFlag = C.BPF_F_ALLOW_OVERRIDE - BPFFAllowMulti AttachFlag = C.BPF_F_ALLOW_MULTI - BPFFReplace AttachFlag = C.BPF_F_REPLACE -) - -// AttachGenericFD attaches the BPFProgram to a targetFd at the specified attachType hook. -func (p *BPFProg) AttachGenericFD(targetFd int, attachType BPFAttachType, flags AttachFlag) error { - progFd := C.bpf_program__fd(p.prog) - errC := C.bpf_prog_attach(progFd, C.int(targetFd), C.enum_bpf_attach_type(int(attachType)), C.uint(uint(flags))) - if errC < 0 { - return fmt.Errorf("failed to attach: %w", syscall.Errno(-errC)) - } - return nil -} - -// DetachGenericFD detaches the BPFProgram associated with the targetFd at the hook specified by attachType. -func (p *BPFProg) DetachGenericFD(targetFd int, attachType BPFAttachType) error { - progFd := C.bpf_program__fd(p.prog) - errC := C.bpf_prog_detach2(progFd, C.int(targetFd), C.enum_bpf_attach_type(int(attachType))) - if errC < 0 { - return fmt.Errorf("failed to detach: %w", syscall.Errno(-errC)) - } - return nil -} - -var eventChannels = newRWArray(maxEventChannels) - -func (m *Module) InitRingBuf(mapName string, eventsChan chan []byte) (*RingBuffer, error) { - bpfMap, err := m.GetMap(mapName) - if err != nil { - return nil, err - } - - if eventsChan == nil { - return nil, fmt.Errorf("events channel can not be nil") - } - - slot := eventChannels.put(eventsChan) - if slot == -1 { - return nil, fmt.Errorf("max ring buffers reached") - } - - rb := C.cgo_init_ring_buf(C.int(bpfMap.FileDescriptor()), C.uintptr_t(slot)) - if rb == nil { - return nil, fmt.Errorf("failed to initialize ring buffer") - } - - ringBuf := &RingBuffer{ - rb: rb, - bpfMap: bpfMap, - slot: uint(slot), - } - m.ringBufs = append(m.ringBufs, ringBuf) - return ringBuf, nil -} - -// Poll will wait until timeout in milliseconds to gather -// data from the ring buffer. -func (rb *RingBuffer) Poll(timeout int) { - rb.stop = make(chan struct{}) - rb.wg.Add(1) - go rb.poll(timeout) -} - -// Deprecated: use RingBuffer.Poll() instead. -func (rb *RingBuffer) Start() { - rb.Poll(300) -} - -func (rb *RingBuffer) Stop() { - if rb.stop != nil { - // Tell the poll goroutine that it's time to exit - close(rb.stop) - - // The event channel should be drained here since the consumer - // may have stopped at this point. Failure to drain it will - // result in a deadlock: the channel will fill up and the poll - // goroutine will block in the callback. - eventChan := eventChannels.get(rb.slot).(chan []byte) - go func() { - // revive:disable:empty-block - for range eventChan { - } - // revive:enable:empty-block - }() - - // Wait for the poll goroutine to exit - rb.wg.Wait() - - // Close the channel -- this is useful for the consumer but - // also to terminate the drain goroutine above. - close(eventChan) - - // This allows Stop() to be called multiple times safely - rb.stop = nil - } -} - -func (rb *RingBuffer) Close() { - if rb.closed { - return - } - rb.Stop() - C.ring_buffer__free(rb.rb) - eventChannels.remove(rb.slot) - rb.closed = true -} - -func (rb *RingBuffer) isStopped() bool { - select { - case <-rb.stop: - return true - default: - return false - } -} - -func (rb *RingBuffer) poll(timeout int) error { - defer rb.wg.Done() - - for { - err := C.ring_buffer__poll(rb.rb, C.int(timeout)) - if rb.isStopped() { - break - } - - if err < 0 { - if syscall.Errno(-err) == syscall.EINTR { - continue - } - return fmt.Errorf("error polling ring buffer: %d", err) - } - } - return nil -} - -func (m *Module) InitPerfBuf(mapName string, eventsChan chan []byte, lostChan chan uint64, pageCnt int) (*PerfBuffer, error) { - bpfMap, err := m.GetMap(mapName) - if err != nil { - return nil, fmt.Errorf("failed to init perf buffer: %v", err) - } - if eventsChan == nil { - return nil, fmt.Errorf("failed to init perf buffer: events channel can not be nil") - } - - perfBuf := &PerfBuffer{ - bpfMap: bpfMap, - eventsChan: eventsChan, - lostChan: lostChan, - } - - slot := eventChannels.put(perfBuf) - if slot == -1 { - return nil, fmt.Errorf("max number of ring/perf buffers reached") - } - - pb := C.cgo_init_perf_buf(C.int(bpfMap.FileDescriptor()), C.int(pageCnt), C.uintptr_t(slot)) - if pb == nil { - eventChannels.remove(uint(slot)) - return nil, fmt.Errorf("failed to initialize perf buffer") - } - - perfBuf.pb = pb - perfBuf.slot = uint(slot) - - m.perfBufs = append(m.perfBufs, perfBuf) - return perfBuf, nil -} - -// Poll will wait until timeout in milliseconds to gather -// data from the perf buffer. -func (pb *PerfBuffer) Poll(timeout int) { - pb.stop = make(chan struct{}) - pb.wg.Add(1) - go pb.poll(timeout) -} - -// Deprecated: use PerfBuffer.Poll() instead. -func (pb *PerfBuffer) Start() { - pb.Poll(300) -} - -func (pb *PerfBuffer) Stop() { - if pb.stop != nil { - // Tell the poll goroutine that it's time to exit - close(pb.stop) - - // The event and lost channels should be drained here since the consumer - // may have stopped at this point. Failure to drain it will - // result in a deadlock: the channel will fill up and the poll - // goroutine will block in the callback. - go func() { - // revive:disable:empty-block - for range pb.eventsChan { - } - - if pb.lostChan != nil { - for range pb.lostChan { - } - } - // revive:enable:empty-block - }() - - // Wait for the poll goroutine to exit - pb.wg.Wait() - - // Close the channel -- this is useful for the consumer but - // also to terminate the drain goroutine above. - close(pb.eventsChan) - if pb.lostChan != nil { - close(pb.lostChan) - } - - // This allows Stop() to be called multiple times safely - pb.stop = nil - } -} - -func (pb *PerfBuffer) Close() { - if pb.closed { - return - } - pb.Stop() - C.perf_buffer__free(pb.pb) - eventChannels.remove(pb.slot) - pb.closed = true -} - -// todo: consider writing the perf polling in go as c to go calls (callback) are expensive -func (pb *PerfBuffer) poll(timeout int) error { - defer pb.wg.Done() - - for { - select { - case <-pb.stop: - return nil - default: - err := C.perf_buffer__poll(pb.pb, C.int(timeout)) - if err < 0 { - if syscall.Errno(-err) == syscall.EINTR { - continue - } - return fmt.Errorf("error polling perf buffer: %d", err) - } - } - } -} - -type TcAttachPoint uint32 - -const ( - BPFTcIngress TcAttachPoint = C.BPF_TC_INGRESS - BPFTcEgress TcAttachPoint = C.BPF_TC_EGRESS - BPFTcIngressEgress TcAttachPoint = C.BPF_TC_INGRESS | C.BPF_TC_EGRESS - BPFTcCustom TcAttachPoint = C.BPF_TC_CUSTOM -) - -type TcFlags uint32 - -const ( - BpfTcFReplace TcFlags = C.BPF_TC_F_REPLACE -) - -type TcHook struct { - hook *C.struct_bpf_tc_hook -} - -type TcOpts struct { - ProgFd int - Flags TcFlags - ProgId uint - Handle uint - Priority uint -} - -func tcOptsToC(tcOpts *TcOpts) *C.struct_bpf_tc_opts { - if tcOpts == nil { - return nil - } - opts := C.struct_bpf_tc_opts{} - opts.sz = C.sizeof_struct_bpf_tc_opts - opts.prog_fd = C.int(tcOpts.ProgFd) - opts.flags = C.uint(tcOpts.Flags) - opts.prog_id = C.uint(tcOpts.ProgId) - opts.handle = C.uint(tcOpts.Handle) - opts.priority = C.uint(tcOpts.Priority) - - return &opts -} - -func tcOptsFromC(tcOpts *TcOpts, opts *C.struct_bpf_tc_opts) { - if opts == nil { - return - } - tcOpts.ProgFd = int(opts.prog_fd) - tcOpts.Flags = TcFlags(opts.flags) - tcOpts.ProgId = uint(opts.prog_id) - tcOpts.Handle = uint(opts.handle) - tcOpts.Priority = uint(opts.priority) -} - -func (m *Module) TcHookInit() *TcHook { - hook := C.struct_bpf_tc_hook{} - hook.sz = C.sizeof_struct_bpf_tc_hook - - return &TcHook{ - hook: &hook, - } -} - -func (hook *TcHook) SetInterfaceByIndex(ifaceIdx int) { - hook.hook.ifindex = C.int(ifaceIdx) -} - -func (hook *TcHook) SetInterfaceByName(ifaceName string) error { - iface, err := net.InterfaceByName(ifaceName) - if err != nil { - return err - } - hook.hook.ifindex = C.int(iface.Index) - - return nil -} - -func (hook *TcHook) GetInterfaceIndex() int { - return int(hook.hook.ifindex) -} - -func (hook *TcHook) SetAttachPoint(attachPoint TcAttachPoint) { - hook.hook.attach_point = uint32(attachPoint) -} - -func (hook *TcHook) SetParent(a int, b int) { - parent := (((a) << 16) & 0xFFFF0000) | ((b) & 0x0000FFFF) - hook.hook.parent = C.uint(parent) -} - -func (hook *TcHook) Create() error { - errC := C.bpf_tc_hook_create(hook.hook) - if errC < 0 { - return fmt.Errorf("failed to create tc hook: %w", syscall.Errno(-errC)) - } - - return nil -} - -func (hook *TcHook) Destroy() error { - errC := C.bpf_tc_hook_destroy(hook.hook) - if errC < 0 { - return fmt.Errorf("failed to destroy tc hook: %w", syscall.Errno(-errC)) - } - - return nil -} - -func (hook *TcHook) Attach(tcOpts *TcOpts) error { - opts := tcOptsToC(tcOpts) - errC := C.bpf_tc_attach(hook.hook, opts) - if errC < 0 { - return fmt.Errorf("failed to attach tc hook: %w", syscall.Errno(-errC)) - } - tcOptsFromC(tcOpts, opts) - - return nil -} - -func (hook *TcHook) Detach(tcOpts *TcOpts) error { - opts := tcOptsToC(tcOpts) - errC := C.bpf_tc_detach(hook.hook, opts) - if errC < 0 { - return fmt.Errorf("failed to detach tc hook: %w", syscall.Errno(-errC)) - } - tcOptsFromC(tcOpts, opts) - - return nil -} - -func (hook *TcHook) Query(tcOpts *TcOpts) error { - opts := tcOptsToC(tcOpts) - errC := C.bpf_tc_query(hook.hook, opts) - if errC < 0 { - return fmt.Errorf("failed to query tc hook: %w", syscall.Errno(-errC)) - } - tcOptsFromC(tcOpts, opts) - - return nil -} +// Support +// -func BPFMapTypeIsSupported(mapType MapType) (bool, error) { - cSupported := C.libbpf_probe_bpf_map_type(C.enum_bpf_map_type(int(mapType)), nil) +func BPFProgramTypeIsSupported(progType BPFProgType) (bool, error) { + cSupported := C.libbpf_probe_bpf_prog_type(C.enum_bpf_prog_type(int(progType)), nil) if cSupported < 1 { return false, syscall.Errno(-cSupported) } return cSupported == 1, nil } -func BPFProgramTypeIsSupported(progType BPFProgType) (bool, error) { - cSupported := C.libbpf_probe_bpf_prog_type(C.enum_bpf_prog_type(int(progType)), nil) +func BPFMapTypeIsSupported(mapType MapType) (bool, error) { + cSupported := C.libbpf_probe_bpf_map_type(C.enum_bpf_map_type(int(mapType)), nil) if cSupported < 1 { return false, syscall.Errno(-cSupported) } return cSupported == 1, nil } +// +// Misc +// + func NumPossibleCPUs() (int, error) { numCPUs, errC := C.libbpf_num_possible_cpus() if numCPUs < 0 { diff --git a/link-reader.go b/link-reader.go new file mode 100644 index 00000000..da458c07 --- /dev/null +++ b/link-reader.go @@ -0,0 +1,27 @@ +package libbpfgo + +/* +#cgo LDFLAGS: -lelf -lz +#include "libbpfgo.h" +*/ +import "C" + +import "syscall" + +// +// BPFLinkReader +// + +// BPFLinkReader read data from a BPF link +type BPFLinkReader struct { + l *BPFLink + fd int +} + +func (i *BPFLinkReader) Read(p []byte) (n int, err error) { + return syscall.Read(i.fd, p) +} + +func (i *BPFLinkReader) Close() error { + return syscall.Close(i.fd) +} diff --git a/link.go b/link.go new file mode 100644 index 00000000..2b1276fe --- /dev/null +++ b/link.go @@ -0,0 +1,123 @@ +package libbpfgo + +/* +#cgo LDFLAGS: -lelf -lz +#include "libbpfgo.h" +*/ +import "C" + +import ( + "fmt" + "syscall" + "unsafe" +) + +// +// LinkType +// + +type LinkType int + +const ( + Tracepoint LinkType = iota + RawTracepoint + Kprobe + Kretprobe + LSM + PerfEvent + Uprobe + Uretprobe + Tracing + XDP + Cgroup + CgroupLegacy + Netns + Iter +) + +// +// BPFLinkLegacy +// + +type BPFLinkLegacy struct { + attachType BPFAttachType + cgroupDir string +} + +// +// BPFLink +// + +type BPFLink struct { + link *C.struct_bpf_link + prog *BPFProg + linkType LinkType + eventName string + legacy *BPFLinkLegacy // if set, this is a fake BPFLink +} + +func (l *BPFLink) DestroyLegacy(linkType LinkType) error { + switch l.linkType { + case CgroupLegacy: + return l.prog.DetachCgroupLegacy( + l.legacy.cgroupDir, + l.legacy.attachType, + ) + } + return fmt.Errorf("unable to destroy legacy link") +} + +func (l *BPFLink) Destroy() error { + if l.legacy != nil { + return l.DestroyLegacy(l.linkType) + } + if ret := C.bpf_link__destroy(l.link); ret < 0 { + return syscall.Errno(-ret) + } + l.link = nil + return nil +} + +func (l *BPFLink) FileDescriptor() int { + return int(C.bpf_link__fd(l.link)) +} + +// Deprecated: use BPFLink.FileDescriptor() instead. +func (l *BPFLink) GetFd() int { + return l.FileDescriptor() +} + +func (l *BPFLink) Pin(pinPath string) error { + path := C.CString(pinPath) + errC := C.bpf_link__pin(l.link, path) + C.free(unsafe.Pointer(path)) + if errC != 0 { + return fmt.Errorf("failed to pin link %s to path %s: %w", l.eventName, pinPath, syscall.Errno(-errC)) + } + return nil +} + +func (l *BPFLink) Unpin(pinPath string) error { + path := C.CString(pinPath) + errC := C.bpf_link__unpin(l.link) + C.free(unsafe.Pointer(path)) + if errC != 0 { + return fmt.Errorf("failed to unpin link %s from path %s: %w", l.eventName, pinPath, syscall.Errno(-errC)) + } + return nil +} + +// +// BPF Link Reader (low-level) +// + +func (l *BPFLink) Reader() (*BPFLinkReader, error) { + fd, errno := C.bpf_iter_create(C.int(l.FileDescriptor())) + if fd < 0 { + return nil, fmt.Errorf("failed to create reader: %w", errno) + } + return &BPFLinkReader{ + l: l, + fd: int(uintptr(fd)), + }, nil +} diff --git a/module-iterator.go b/module-iterator.go new file mode 100644 index 00000000..94b6bd5b --- /dev/null +++ b/module-iterator.go @@ -0,0 +1,79 @@ +package libbpfgo + +/* +#cgo LDFLAGS: -lelf -lz +#include "libbpfgo.h" +*/ +import "C" + +// +// BPFObjectIterator (Module Iterator) +// + +// BPFObjectProgramIterator iterates over programs and maps in a BPF object +type BPFObjectIterator struct { + m *Module + prevProg *BPFProg + prevMap *BPFMap +} + +func (it *BPFObjectIterator) NextMap() *BPFMap { + var startMap *C.struct_bpf_map + if it.prevMap != nil && it.prevMap.bpfMap != nil { + startMap = it.prevMap.bpfMap + } + + m := C.bpf_object__next_map(it.m.obj, startMap) + if m == nil { + return nil + } + + bpfMap := &BPFMap{ + bpfMap: m, + module: it.m, + } + it.prevMap = bpfMap + + if !bpfMap.module.loaded { + bpfMap.bpfMapLow = &BPFMapLow{ + fd: -1, + info: &BPFMapInfo{}, + } + + return bpfMap + } + + fd := bpfMap.FileDescriptor() + info, err := GetMapInfoByFD(fd) + if err != nil { + return nil + } + + bpfMap.bpfMapLow = &BPFMapLow{ + fd: fd, + info: info, + } + + return bpfMap +} + +func (it *BPFObjectIterator) NextProgram() *BPFProg { + var startProg *C.struct_bpf_program + if it.prevProg != nil && it.prevProg.prog != nil { + startProg = it.prevProg.prog + } + + p := C.bpf_object__next_program(it.m.obj, startProg) + if p == nil { + return nil + } + cName := C.bpf_program__name(p) + + prog := &BPFProg{ + name: C.GoString(cName), + prog: p, + module: it.m, + } + it.prevProg = prog + return prog +} diff --git a/module.go b/module.go new file mode 100644 index 00000000..2b67b58f --- /dev/null +++ b/module.go @@ -0,0 +1,391 @@ +package libbpfgo + +/* +#cgo LDFLAGS: -lelf -lz +#include "libbpfgo.h" +*/ +import "C" + +import ( + "bytes" + "debug/elf" + "encoding/binary" + "errors" + "fmt" + "syscall" + "unsafe" +) + +// +// Module (BPF Object) +// + +type Module struct { + obj *C.struct_bpf_object + links []*BPFLink + perfBufs []*PerfBuffer + ringBufs []*RingBuffer + elf *elf.File + loaded bool +} + +// +// New Module Helpers +// + +type NewModuleArgs struct { + KConfigFilePath string + BTFObjPath string + BPFObjName string + BPFObjPath string + BPFObjBuff []byte + SkipMemlockBump bool +} + +func NewModuleFromFile(bpfObjPath string) (*Module, error) { + return NewModuleFromFileArgs(NewModuleArgs{ + BPFObjPath: bpfObjPath, + }) +} + +func NewModuleFromFileArgs(args NewModuleArgs) (*Module, error) { + f, err := elf.Open(args.BPFObjPath) + if err != nil { + return nil, err + } + C.cgo_libbpf_set_print_fn() + + // If skipped, we rely on libbpf to do the bumping if deemed necessary + if !args.SkipMemlockBump { + // TODO: remove this once libbpf memory limit bump issue is solved + if err := bumpMemlockRlimit(); err != nil { + return nil, err + } + } + + opts := C.struct_bpf_object_open_opts{} + opts.sz = C.sizeof_struct_bpf_object_open_opts + + bpfFile := C.CString(args.BPFObjPath) + defer C.free(unsafe.Pointer(bpfFile)) + + // instruct libbpf to use user provided kernel BTF file + if args.BTFObjPath != "" { + btfFile := C.CString(args.BTFObjPath) + opts.btf_custom_path = btfFile + defer C.free(unsafe.Pointer(btfFile)) + } + + // instruct libbpf to use user provided KConfigFile + if args.KConfigFilePath != "" { + kConfigFile := C.CString(args.KConfigFilePath) + opts.kconfig = kConfigFile + defer C.free(unsafe.Pointer(kConfigFile)) + } + + obj, errno := C.bpf_object__open_file(bpfFile, &opts) + if obj == nil { + return nil, fmt.Errorf("failed to open BPF object at path %s: %w", args.BPFObjPath, errno) + } + + return &Module{ + obj: obj, + elf: f, + }, nil +} + +func NewModuleFromBuffer(bpfObjBuff []byte, bpfObjName string) (*Module, error) { + return NewModuleFromBufferArgs(NewModuleArgs{ + BPFObjBuff: bpfObjBuff, + BPFObjName: bpfObjName, + }) +} + +func NewModuleFromBufferArgs(args NewModuleArgs) (*Module, error) { + f, err := elf.NewFile(bytes.NewReader(args.BPFObjBuff)) + if err != nil { + return nil, err + } + C.cgo_libbpf_set_print_fn() + + // TODO: remove this once libbpf memory limit bump issue is solved + if err := bumpMemlockRlimit(); err != nil { + return nil, err + } + + if args.BTFObjPath == "" { + args.BTFObjPath = "/sys/kernel/btf/vmlinux" + } + + cBTFFilePath := C.CString(args.BTFObjPath) + defer C.free(unsafe.Pointer(cBTFFilePath)) + cKconfigPath := C.CString(args.KConfigFilePath) + defer C.free(unsafe.Pointer(cKconfigPath)) + cBPFObjName := C.CString(args.BPFObjName) + defer C.free(unsafe.Pointer(cBPFObjName)) + cBPFBuff := unsafe.Pointer(C.CBytes(args.BPFObjBuff)) + defer C.free(cBPFBuff) + cBPFBuffSize := C.size_t(len(args.BPFObjBuff)) + + if len(args.KConfigFilePath) <= 2 { + cKconfigPath = nil + } + + cOpts, errno := C.cgo_bpf_object_open_opts_new(cBTFFilePath, cKconfigPath, cBPFObjName) + if cOpts == nil { + return nil, fmt.Errorf("failed to create bpf_object_open_opts to %s: %w", args.BPFObjName, errno) + } + defer C.cgo_bpf_object_open_opts_free(cOpts) + + obj, errno := C.bpf_object__open_mem(cBPFBuff, cBPFBuffSize, cOpts) + if obj == nil { + return nil, fmt.Errorf("failed to open BPF object %s: %w", args.BPFObjName, errno) + } + + return &Module{ + obj: obj, + elf: f, + }, nil +} + +// NOTE: libbpf has started raising limits by default but, unfortunately, that +// seems to be failing in current libbpf version. The memory limit bump might be +// removed once this is sorted out. +func bumpMemlockRlimit() error { + var rLimit syscall.Rlimit + rLimit.Max = 512 << 20 /* 512 MBs */ + rLimit.Cur = 512 << 20 /* 512 MBs */ + err := syscall.Setrlimit(C.RLIMIT_MEMLOCK, &rLimit) + if err != nil { + return fmt.Errorf("error setting rlimit: %v", err) + } + return nil +} + +// +// Module Methods +// + +func (m *Module) Close() { + for _, pb := range m.perfBufs { + pb.Close() + } + for _, rb := range m.ringBufs { + rb.Close() + } + for _, link := range m.links { + if link.link != nil { + link.Destroy() + } + } + C.bpf_object__close(m.obj) +} + +func (m *Module) BPFLoadObject() error { + ret := C.bpf_object__load(m.obj) + if ret != 0 { + return fmt.Errorf("failed to load BPF object: %w", syscall.Errno(-ret)) + } + m.loaded = true + m.elf.Close() + + return nil +} + +// InitGlobalVariable sets global variables (defined in .data or .rodata) +// in bpf code. It must be called before the BPF object is loaded. +func (m *Module) InitGlobalVariable(name string, value interface{}) error { + if m.loaded { + return errors.New("must be called before the BPF object is loaded") + } + s, err := getGlobalVariableSymbol(m.elf, name) + if err != nil { + return err + } + bpfMap, err := m.GetMap(s.sectionName) + if err != nil { + return err + } + + // get current value + currMapValue, err := bpfMap.InitialValue() + if err != nil { + return err + } + + // generate new value + newMapValue := make([]byte, bpfMap.ValueSize()) + copy(newMapValue, currMapValue) + data := bytes.NewBuffer(nil) + if err := binary.Write(data, s.byteOrder, value); err != nil { + return err + } + varValue := data.Bytes() + start := s.offset + end := s.offset + len(varValue) + if len(varValue) > s.size || end > bpfMap.ValueSize() { + return errors.New("invalid value") + } + copy(newMapValue[start:end], varValue) + + // save new value + err = bpfMap.SetInitialValue(unsafe.Pointer(&newMapValue[0])) + return err +} + +func (m *Module) GetMap(mapName string) (*BPFMap, error) { + cs := C.CString(mapName) + bpfMapC, errno := C.bpf_object__find_map_by_name(m.obj, cs) + C.free(unsafe.Pointer(cs)) + if bpfMapC == nil { + return nil, fmt.Errorf("failed to find BPF map %s: %w", mapName, errno) + } + + bpfMap := &BPFMap{ + bpfMap: bpfMapC, + module: m, + } + + if !m.loaded { + bpfMap.bpfMapLow = &BPFMapLow{ + fd: -1, + info: &BPFMapInfo{}, + } + + return bpfMap, nil + } + + fd := bpfMap.FileDescriptor() + info, err := GetMapInfoByFD(fd) + if err != nil { + // Compatibility Note: Some older kernels lack BTF (BPF Type Format) + // support for specific BPF map types. In such scenarios, libbpf may + // fail (EPERM) when attempting to retrieve information for these maps. + // Reference: https://elixir.bootlin.com/linux/v5.15.75/source/tools/lib/bpf/gen_loader.c#L401 + // + // However, we can still get some map info from the BPF map high level API. + bpfMap.bpfMapLow = &BPFMapLow{ + fd: fd, + info: &BPFMapInfo{ + Type: bpfMap.Type(), + ID: 0, + KeySize: uint32(bpfMap.KeySize()), + ValueSize: uint32(bpfMap.ValueSize()), + MaxEntries: bpfMap.MaxEntries(), + MapFlags: uint32(bpfMap.MapFlags()), + Name: bpfMap.Name(), + IfIndex: bpfMap.IfIndex(), + BTFVmlinuxValueTypeID: 0, + NetnsDev: 0, + NetnsIno: 0, + BTFID: 0, + BTFKeyTypeID: 0, + BTFValueTypeID: 0, + MapExtra: bpfMap.MapExtra(), + }, + } + + return bpfMap, nil + } + + bpfMap.bpfMapLow = &BPFMapLow{ + fd: fd, + info: info, + } + + return bpfMap, nil +} + +func (m *Module) GetProgram(progName string) (*BPFProg, error) { + cs := C.CString(progName) + prog, errno := C.bpf_object__find_program_by_name(m.obj, cs) + C.free(unsafe.Pointer(cs)) + if prog == nil { + return nil, fmt.Errorf("failed to find BPF program %s: %w", progName, errno) + } + + return &BPFProg{ + name: progName, + prog: prog, + module: m, + }, nil +} + +func (m *Module) InitRingBuf(mapName string, eventsChan chan []byte) (*RingBuffer, error) { + bpfMap, err := m.GetMap(mapName) + if err != nil { + return nil, err + } + + if eventsChan == nil { + return nil, fmt.Errorf("events channel can not be nil") + } + + slot := eventChannels.put(eventsChan) + if slot == -1 { + return nil, fmt.Errorf("max ring buffers reached") + } + + rb := C.cgo_init_ring_buf(C.int(bpfMap.FileDescriptor()), C.uintptr_t(slot)) + if rb == nil { + return nil, fmt.Errorf("failed to initialize ring buffer") + } + + ringBuf := &RingBuffer{ + rb: rb, + bpfMap: bpfMap, + slot: uint(slot), + } + m.ringBufs = append(m.ringBufs, ringBuf) + return ringBuf, nil +} + +func (m *Module) InitPerfBuf(mapName string, eventsChan chan []byte, lostChan chan uint64, pageCnt int) (*PerfBuffer, error) { + bpfMap, err := m.GetMap(mapName) + if err != nil { + return nil, fmt.Errorf("failed to init perf buffer: %v", err) + } + if eventsChan == nil { + return nil, fmt.Errorf("failed to init perf buffer: events channel can not be nil") + } + + perfBuf := &PerfBuffer{ + bpfMap: bpfMap, + eventsChan: eventsChan, + lostChan: lostChan, + } + + slot := eventChannels.put(perfBuf) + if slot == -1 { + return nil, fmt.Errorf("max number of ring/perf buffers reached") + } + + pb := C.cgo_init_perf_buf(C.int(bpfMap.FileDescriptor()), C.int(pageCnt), C.uintptr_t(slot)) + if pb == nil { + eventChannels.remove(uint(slot)) + return nil, fmt.Errorf("failed to initialize perf buffer") + } + + perfBuf.pb = pb + perfBuf.slot = uint(slot) + + m.perfBufs = append(m.perfBufs, perfBuf) + return perfBuf, nil +} + +func (m *Module) TcHookInit() *TcHook { + hook := C.struct_bpf_tc_hook{} + hook.sz = C.sizeof_struct_bpf_tc_hook + + return &TcHook{ + hook: &hook, + } +} + +func (m *Module) Iterator() *BPFObjectIterator { + return &BPFObjectIterator{ + m: m, + prevProg: nil, + prevMap: nil, + } +} diff --git a/prog-common.go b/prog-common.go new file mode 100644 index 00000000..29ead06d --- /dev/null +++ b/prog-common.go @@ -0,0 +1,172 @@ +package libbpfgo + +/* +#cgo LDFLAGS: -lelf -lz +#include "libbpfgo.h" +*/ +import "C" + +// +// BPFProgType +// + +// BPFProgType is an enum as defined in https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/bpf.h +type BPFProgType uint32 + +const ( + BPFProgTypeUnspec BPFProgType = iota + BPFProgTypeSocketFilter + BPFProgTypeKprobe + BPFProgTypeSchedCls + BPFProgTypeSchedAct + BPFProgTypeTracepoint + BPFProgTypeXdp + BPFProgTypePerfEvent + BPFProgTypeCgroupSkb + BPFProgTypeCgroupSock + BPFProgTypeLwtIn + BPFProgTypeLwtOut + BPFProgTypeLwtXmit + BPFProgTypeSockOps + BPFProgTypeSkSkb + BPFProgTypeCgroupDevice + BPFProgTypeSkMsg + BPFProgTypeRawTracepoint + BPFProgTypeCgroupSockAddr + BPFProgTypeLwtSeg6Local + BPFProgTypeLircMode2 + BPFProgTypeSkReuseport + BPFProgTypeFlowDissector + BPFProgTypeCgroupSysctl + BPFProgTypeRawTracepointWritable + BPFProgTypeCgroupSockopt + BPFProgTypeTracing + BPFProgTypeStructOps + BPFProgTypeExt + BPFProgTypeLsm + BPFProgTypeSkLookup + BPFProgTypeSyscall +) + +func (b BPFProgType) Value() uint64 { return uint64(b) } + +func (b BPFProgType) String() (str string) { + x := map[BPFProgType]string{ + BPFProgTypeUnspec: "BPF_PROG_TYPE_UNSPEC", + BPFProgTypeSocketFilter: "BPF_PROG_TYPE_SOCKET_FILTER", + BPFProgTypeKprobe: "BPF_PROG_TYPE_KPROBE", + BPFProgTypeSchedCls: "BPF_PROG_TYPE_SCHED_CLS", + BPFProgTypeSchedAct: "BPF_PROG_TYPE_SCHED_ACT", + BPFProgTypeTracepoint: "BPF_PROG_TYPE_TRACEPOINT", + BPFProgTypeXdp: "BPF_PROG_TYPE_XDP", + BPFProgTypePerfEvent: "BPF_PROG_TYPE_PERF_EVENT", + BPFProgTypeCgroupSkb: "BPF_PROG_TYPE_CGROUP_SKB", + BPFProgTypeCgroupSock: "BPF_PROG_TYPE_CGROUP_SOCK", + BPFProgTypeLwtIn: "BPF_PROG_TYPE_LWT_IN", + BPFProgTypeLwtOut: "BPF_PROG_TYPE_LWT_OUT", + BPFProgTypeLwtXmit: "BPF_PROG_TYPE_LWT_XMIT", + BPFProgTypeSockOps: "BPF_PROG_TYPE_SOCK_OPS", + BPFProgTypeSkSkb: "BPF_PROG_TYPE_SK_SKB", + BPFProgTypeCgroupDevice: "BPF_PROG_TYPE_CGROUP_DEVICE", + BPFProgTypeSkMsg: "BPF_PROG_TYPE_SK_MSG", + BPFProgTypeRawTracepoint: "BPF_PROG_TYPE_RAW_TRACEPOINT", + BPFProgTypeCgroupSockAddr: "BPF_PROG_TYPE_CGROUP_SOCK_ADDR", + BPFProgTypeLwtSeg6Local: "BPF_PROG_TYPE_LWT_SEG6LOCAL", + BPFProgTypeLircMode2: "BPF_PROG_TYPE_LIRC_MODE2", + BPFProgTypeSkReuseport: "BPF_PROG_TYPE_SK_REUSEPORT", + BPFProgTypeFlowDissector: "BPF_PROG_TYPE_FLOW_DISSECTOR", + BPFProgTypeCgroupSysctl: "BPF_PROG_TYPE_CGROUP_SYSCTL", + BPFProgTypeRawTracepointWritable: "BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE", + BPFProgTypeCgroupSockopt: "BPF_PROG_TYPE_CGROUP_SOCKOPT", + BPFProgTypeTracing: "BPF_PROG_TYPE_TRACING", + BPFProgTypeStructOps: "BPF_PROG_TYPE_STRUCT_OPS", + BPFProgTypeExt: "BPF_PROG_TYPE_EXT", + BPFProgTypeLsm: "BPF_PROG_TYPE_LSM", + BPFProgTypeSkLookup: "BPF_PROG_TYPE_SK_LOOKUP", + BPFProgTypeSyscall: "BPF_PROG_TYPE_SYSCALL", + } + str = x[b] + if str == "" { + str = BPFProgTypeUnspec.String() + } + return str +} + +// +// BPFAttachType +// + +type BPFAttachType uint32 + +const ( + BPFAttachTypeCgroupInetIngress BPFAttachType = iota + BPFAttachTypeCgroupInetEgress + BPFAttachTypeCgroupInetSockCreate + BPFAttachTypeCgroupSockOps + BPFAttachTypeSKSKBStreamParser + BPFAttachTypeSKSKBStreamVerdict + BPFAttachTypeCgroupDevice + BPFAttachTypeSKMSGVerdict + BPFAttachTypeCgroupInet4Bind + BPFAttachTypeCgroupInet6Bind + BPFAttachTypeCgroupInet4Connect + BPFAttachTypeCgroupInet6Connect + BPFAttachTypeCgroupInet4PostBind + BPFAttachTypeCgroupInet6PostBind + BPFAttachTypeCgroupUDP4SendMsg + BPFAttachTypeCgroupUDP6SendMsg + BPFAttachTypeLircMode2 + BPFAttachTypeFlowDissector + BPFAttachTypeCgroupSysctl + BPFAttachTypeCgroupUDP4RecvMsg + BPFAttachTypeCgroupUDP6RecvMsg + BPFAttachTypeCgroupGetSockOpt + BPFAttachTypeCgroupSetSockOpt + BPFAttachTypeTraceRawTP + BPFAttachTypeTraceFentry + BPFAttachTypeTraceFexit + BPFAttachTypeModifyReturn + BPFAttachTypeLSMMac + BPFAttachTypeTraceIter + BPFAttachTypeCgroupInet4GetPeerName + BPFAttachTypeCgroupInet6GetPeerName + BPFAttachTypeCgroupInet4GetSockName + BPFAttachTypeCgroupInet6GetSockName + BPFAttachTypeXDPDevMap + BPFAttachTypeCgroupInetSockRelease + BPFAttachTypeXDPCPUMap + BPFAttachTypeSKLookup + BPFAttachTypeXDP + BPFAttachTypeSKSKBVerdict + BPFAttachTypeSKReusePortSelect + BPFAttachTypeSKReusePortSelectorMigrate + BPFAttachTypePerfEvent + BPFAttachTypeTraceKprobeMulti +) + +// +// BPFCgroupIterOrder +// + +type BPFCgroupIterOrder uint32 + +const ( + BPFIterOrderUnspec BPFCgroupIterOrder = iota + BPFIterSelfOnly + BPFIterDescendantsPre + BPFIterDescendantsPost + BPFIterAncestorsUp +) + +// +// AttachFlag +// + +type AttachFlag uint32 + +const ( + BPFFNone AttachFlag = 0 + BPFFAllowOverride AttachFlag = C.BPF_F_ALLOW_OVERRIDE + BPFFAllowMulti AttachFlag = C.BPF_F_ALLOW_MULTI + BPFFReplace AttachFlag = C.BPF_F_REPLACE +) diff --git a/prog.go b/prog.go new file mode 100644 index 00000000..267e4d61 --- /dev/null +++ b/prog.go @@ -0,0 +1,507 @@ +package libbpfgo + +/* +#cgo LDFLAGS: -lelf -lz +#include "libbpfgo.h" +*/ +import "C" + +import ( + "fmt" + "net" + "path/filepath" + "strings" + "syscall" + "unsafe" +) + +// +// BPFProg +// + +type BPFProg struct { + name string + prog *C.struct_bpf_program + module *Module + pinnedPath string +} + +func (p *BPFProg) FileDescriptor() int { + return int(C.bpf_program__fd(p.prog)) +} + +// Deprecated: use BPFProg.FileDescriptor() instead. +func (p *BPFProg) GetFd() int { + return p.FileDescriptor() +} + +func (p *BPFProg) Pin(path string) error { + absPath, err := filepath.Abs(path) + if err != nil { + return fmt.Errorf("invalid path: %s: %v", path, err) + } + + cs := C.CString(absPath) + ret := C.bpf_program__pin(p.prog, cs) + C.free(unsafe.Pointer(cs)) + if ret != 0 { + return fmt.Errorf("failed to pin program %s to %s: %w", p.name, path, syscall.Errno(-ret)) + } + p.pinnedPath = absPath + return nil +} + +func (p *BPFProg) Unpin(path string) error { + cs := C.CString(path) + ret := C.bpf_program__unpin(p.prog, cs) + C.free(unsafe.Pointer(cs)) + if ret != 0 { + return fmt.Errorf("failed to unpin program %s to %s: %w", p.name, path, syscall.Errno(-ret)) + } + p.pinnedPath = "" + return nil +} + +func (p *BPFProg) GetModule() *Module { + return p.module +} + +func (p *BPFProg) Name() string { + return C.GoString(C.bpf_program__name(p.prog)) +} + +// Deprecated: use BPFProg.Name() instead. +func (p *BPFProg) GetName() string { + return p.Name() +} + +func (p *BPFProg) SectionName() string { + return C.GoString(C.bpf_program__section_name(p.prog)) +} + +// Deprecated: use BPFProg.SectionName() instead. +func (p *BPFProg) GetSectionName() string { + return p.SectionName() +} + +func (p *BPFProg) PinPath() string { + return p.pinnedPath // There's no LIBBPF_API for bpf program +} + +// Deprecated: use BPFProg.PinPath() instead. +func (p *BPFProg) GetPinPath() string { + return p.PinPath() +} + +func (p *BPFProg) GetType() BPFProgType { + return BPFProgType(C.bpf_program__type(p.prog)) +} + +func (p *BPFProg) SetAutoload(autoload bool) error { + cbool := C.bool(autoload) + ret := C.bpf_program__set_autoload(p.prog, cbool) + if ret != 0 { + return fmt.Errorf("failed to set bpf program autoload: %w", syscall.Errno(-ret)) + } + return nil +} + +// AttachGeneric is used to attach the BPF program using autodetection +// for the attach target. You can specify the destination in BPF code +// via the SEC() such as `SEC("fentry/some_kernel_func")` +func (p *BPFProg) AttachGeneric() (*BPFLink, error) { + link, errno := C.bpf_program__attach(p.prog) + if link == nil { + return nil, fmt.Errorf("failed to attach program: %w", errno) + } + bpfLink := &BPFLink{ + link: link, + prog: p, + linkType: Tracing, + eventName: fmt.Sprintf("tracing-%s", p.name), + } + return bpfLink, nil +} + +// SetAttachTarget can be used to specify the program and/or function to attach +// the BPF program to. To attach to a kernel function specify attachProgFD as 0 +func (p *BPFProg) SetAttachTarget(attachProgFD int, attachFuncName string) error { + cs := C.CString(attachFuncName) + ret := C.bpf_program__set_attach_target(p.prog, C.int(attachProgFD), cs) + C.free(unsafe.Pointer(cs)) + if ret != 0 { + return fmt.Errorf("failed to set attach target for program %s %s %w", p.name, attachFuncName, syscall.Errno(-ret)) + } + return nil +} + +func (p *BPFProg) SetProgramType(progType BPFProgType) { + C.bpf_program__set_type(p.prog, C.enum_bpf_prog_type(int(progType))) +} + +func (p *BPFProg) SetAttachType(attachType BPFAttachType) { + C.bpf_program__set_expected_attach_type(p.prog, C.enum_bpf_attach_type(int(attachType))) +} + +// getCgroupDirFD returns a file descriptor for a given cgroup2 directory path +func getCgroupDirFD(cgroupV2DirPath string) (int, error) { + // revive:disable + const ( + O_DIRECTORY int = syscall.O_DIRECTORY + O_RDONLY int = syscall.O_RDONLY + ) + // revive:enable + fd, err := syscall.Open(cgroupV2DirPath, O_DIRECTORY|O_RDONLY, 0) + if fd < 0 { + return 0, fmt.Errorf("failed to open cgroupv2 directory path %s: %w", cgroupV2DirPath, err) + } + return fd, nil +} + +// AttachCgroup attaches the BPFProg to a cgroup described by given fd. +func (p *BPFProg) AttachCgroup(cgroupV2DirPath string) (*BPFLink, error) { + cgroupDirFD, err := getCgroupDirFD(cgroupV2DirPath) + if err != nil { + return nil, err + } + defer syscall.Close(cgroupDirFD) + + link, errno := C.bpf_program__attach_cgroup(p.prog, C.int(cgroupDirFD)) + if link == nil { + return nil, fmt.Errorf("failed to attach cgroup on cgroupv2 %s to program %s: %w", cgroupV2DirPath, p.name, errno) + } + + // dirName will be used in bpfLink.eventName. eventName follows a format + // convention and is used to better identify link types and what they are + // linked with in case of errors or similar needs. Having eventName as: + // cgroup-progName-/sys/fs/cgroup/unified/ would look weird so replace it + // to be cgroup-progName-sys-fs-cgroup-unified instead. + dirName := strings.ReplaceAll(cgroupV2DirPath[1:], "/", "-") + bpfLink := &BPFLink{ + link: link, + prog: p, + linkType: Cgroup, + eventName: fmt.Sprintf("cgroup-%s-%s", p.name, dirName), + } + p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil +} + +// AttachCgroupLegacy attaches the BPFProg to a cgroup described by the given +// fd. It first tries to use the most recent attachment method and, if that does +// not work, instead of failing, it tries the legacy way: to attach the cgroup +// eBPF program without previously creating a link. This allows attaching cgroup +// eBPF ingress/egress in older kernels. Note: the first attempt error message +// is filtered out inside libbpf_print_fn() as it is actually a feature probe +// attempt as well. +// +// Related kernel commit: https://github.com/torvalds/linux/commit/af6eea57437a +func (p *BPFProg) AttachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttachType) (*BPFLink, error) { + bpfLink, err := p.AttachCgroup(cgroupV2DirPath) + if err == nil { + return bpfLink, nil + } + // Try the legacy attachment method before fully failing + cgroupDirFD, err := getCgroupDirFD(cgroupV2DirPath) + if err != nil { + return nil, err + } + defer syscall.Close(cgroupDirFD) + progFD := C.bpf_program__fd(p.prog) + ret := C.cgo_bpf_prog_attach_cgroup_legacy(progFD, C.int(cgroupDirFD), C.int(attachType)) + if ret < 0 { + return nil, fmt.Errorf("failed to attach (legacy) program %s to cgroupv2 %s", p.name, cgroupV2DirPath) + } + dirName := strings.ReplaceAll(cgroupV2DirPath[1:], "/", "-") + + bpfLinkLegacy := &BPFLinkLegacy{ + attachType: attachType, + cgroupDir: cgroupV2DirPath, + } + fakeBpfLink := &BPFLink{ + link: nil, // detach/destroy made with progfd + prog: p, + eventName: fmt.Sprintf("cgroup-%s-%s", p.name, dirName), + // info bellow needed for detach (there isn't a real ebpf link) + linkType: CgroupLegacy, + legacy: bpfLinkLegacy, + } + return fakeBpfLink, nil +} + +// DetachCgroupLegacy detaches the BPFProg from a cgroup described by the given +// fd. This is needed because in legacy attachment there is no BPFLink, just a +// fake one (kernel did not support it, nor libbpf). This function should be +// called by the (*BPFLink)->Destroy() function, since BPFLink is emulated (so +// users donĀ“t need to distinguish between regular and legacy cgroup +// detachments). +func (p *BPFProg) DetachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttachType) error { + cgroupDirFD, err := getCgroupDirFD(cgroupV2DirPath) + if err != nil { + return err + } + defer syscall.Close(cgroupDirFD) + progFD := C.bpf_program__fd(p.prog) + ret := C.cgo_bpf_prog_detach_cgroup_legacy(progFD, C.int(cgroupDirFD), C.int(attachType)) + if ret < 0 { + return fmt.Errorf("failed to detach (legacy) program %s from cgroupv2 %s", p.name, cgroupV2DirPath) + } + return nil +} + +func (p *BPFProg) AttachXDP(deviceName string) (*BPFLink, error) { + iface, err := net.InterfaceByName(deviceName) + if err != nil { + return nil, fmt.Errorf("failed to find device by name %s: %w", deviceName, err) + } + link, errno := C.bpf_program__attach_xdp(p.prog, C.int(iface.Index)) + if link == nil { + return nil, fmt.Errorf("failed to attach xdp on device %s to program %s: %w", deviceName, p.name, errno) + } + + bpfLink := &BPFLink{ + link: link, + prog: p, + linkType: XDP, + eventName: fmt.Sprintf("xdp-%s-%s", p.name, deviceName), + } + p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil +} + +func (p *BPFProg) AttachTracepoint(category, name string) (*BPFLink, error) { + tpCategory := C.CString(category) + tpName := C.CString(name) + link, errno := C.bpf_program__attach_tracepoint(p.prog, tpCategory, tpName) + C.free(unsafe.Pointer(tpCategory)) + C.free(unsafe.Pointer(tpName)) + if link == nil { + return nil, fmt.Errorf("failed to attach tracepoint %s to program %s: %w", name, p.name, errno) + } + + bpfLink := &BPFLink{ + link: link, + prog: p, + linkType: Tracepoint, + eventName: name, + } + p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil +} + +func (p *BPFProg) AttachRawTracepoint(tpEvent string) (*BPFLink, error) { + cs := C.CString(tpEvent) + link, errno := C.bpf_program__attach_raw_tracepoint(p.prog, cs) + C.free(unsafe.Pointer(cs)) + if link == nil { + return nil, fmt.Errorf("failed to attach raw tracepoint %s to program %s: %w", tpEvent, p.name, errno) + } + + bpfLink := &BPFLink{ + link: link, + prog: p, + linkType: RawTracepoint, + eventName: tpEvent, + } + p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil +} + +func (p *BPFProg) AttachLSM() (*BPFLink, error) { + link, errno := C.bpf_program__attach_lsm(p.prog) + if link == nil { + return nil, fmt.Errorf("failed to attach lsm to program %s: %w", p.name, errno) + } + + bpfLink := &BPFLink{ + link: link, + prog: p, + linkType: LSM, + } + p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil +} + +func (p *BPFProg) AttachPerfEvent(fd int) (*BPFLink, error) { + link, errno := C.bpf_program__attach_perf_event(p.prog, C.int(fd)) + if link == nil { + return nil, fmt.Errorf("failed to attach perf event to program %s: %w", p.name, errno) + } + + bpfLink := &BPFLink{ + link: link, + prog: p, + linkType: PerfEvent, + } + p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil +} + +// this API should be used for kernels > 4.17 +func (p *BPFProg) AttachKprobe(kp string) (*BPFLink, error) { + return doAttachKprobe(p, kp, false) +} + +// this API should be used for kernels > 4.17 +func (p *BPFProg) AttachKretprobe(kp string) (*BPFLink, error) { + return doAttachKprobe(p, kp, true) +} + +func doAttachKprobe(prog *BPFProg, kp string, isKretprobe bool) (*BPFLink, error) { + cs := C.CString(kp) + cbool := C.bool(isKretprobe) + link, errno := C.bpf_program__attach_kprobe(prog.prog, cbool, cs) + C.free(unsafe.Pointer(cs)) + if link == nil { + return nil, fmt.Errorf("failed to attach %s k(ret)probe to program %s: %w", kp, prog.name, errno) + } + + kpType := Kprobe + if isKretprobe { + kpType = Kretprobe + } + + bpfLink := &BPFLink{ + link: link, + prog: prog, + linkType: kpType, + eventName: kp, + } + prog.module.links = append(prog.module.links, bpfLink) + return bpfLink, nil +} + +func (p *BPFProg) AttachNetns(networkNamespacePath string) (*BPFLink, error) { + fd, err := syscall.Open(networkNamespacePath, syscall.O_RDONLY, 0) + if fd < 0 { + return nil, fmt.Errorf("failed to open network namespace path %s: %w", networkNamespacePath, err) + } + link, errno := C.bpf_program__attach_netns(p.prog, C.int(fd)) + if link == nil { + return nil, fmt.Errorf("failed to attach network namespace on %s to program %s: %w", networkNamespacePath, p.name, errno) + } + + // fileName will be used in bpfLink.eventName. eventName follows a format + // convention and is used to better identify link types and what they are + // linked with in case of errors or similar needs. Having eventName as: + // netns-progName-/proc/self/ns/net would look weird so replace it + // to be netns-progName-proc-self-ns-net instead. + fileName := strings.ReplaceAll(networkNamespacePath[1:], "/", "-") + bpfLink := &BPFLink{ + link: link, + prog: p, + linkType: Netns, + eventName: fmt.Sprintf("netns-%s-%s", p.name, fileName), + } + p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil +} + +type IterOpts struct { + MapFd int + CgroupIterOrder BPFCgroupIterOrder + CgroupFd int + CgroupId uint64 + Tid int + Pid int + PidFd int +} + +func (p *BPFProg) AttachIter(opts IterOpts) (*BPFLink, error) { + mapFd := C.uint(opts.MapFd) + cgroupIterOrder := uint32(opts.CgroupIterOrder) + cgroupFd := C.uint(opts.CgroupFd) + cgroupId := C.ulonglong(opts.CgroupId) + tid := C.uint(opts.Tid) + pid := C.uint(opts.Pid) + pidFd := C.uint(opts.PidFd) + cOpts, errno := C.cgo_bpf_iter_attach_opts_new(mapFd, cgroupIterOrder, cgroupFd, cgroupId, tid, pid, pidFd) + if cOpts == nil { + return nil, fmt.Errorf("failed to create iter_attach_opts to program %s: %w", p.name, errno) + } + defer C.cgo_bpf_iter_attach_opts_free(cOpts) + + link, errno := C.bpf_program__attach_iter(p.prog, cOpts) + if link == nil { + return nil, fmt.Errorf("failed to attach iter to program %s: %w", p.name, errno) + } + eventName := fmt.Sprintf("iter-%s-%d", p.name, opts.MapFd) + bpfLink := &BPFLink{ + link: link, + prog: p, + linkType: Iter, + eventName: eventName, + } + p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil +} + +// AttachUprobe attaches the BPFProgram to entry of the symbol in the library or binary at 'path' +// which can be relative or absolute. A pid can be provided to attach to, or -1 can be specified +// to attach to all processes +func (p *BPFProg) AttachUprobe(pid int, path string, offset uint32) (*BPFLink, error) { + absPath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + + return doAttachUprobe(p, false, pid, absPath, offset) +} + +// AttachURetprobe attaches the BPFProgram to exit of the symbol in the library or binary at 'path' +// which can be relative or absolute. A pid can be provided to attach to, or -1 can be specified +// to attach to all processes +func (p *BPFProg) AttachURetprobe(pid int, path string, offset uint32) (*BPFLink, error) { + absPath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + + return doAttachUprobe(p, true, pid, absPath, offset) +} + +func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offset uint32) (*BPFLink, error) { + retCBool := C.bool(isUretprobe) + pidCint := C.int(pid) + pathCString := C.CString(path) + offsetCsizet := C.size_t(offset) + + link, errno := C.bpf_program__attach_uprobe(prog.prog, retCBool, pidCint, pathCString, offsetCsizet) + C.free(unsafe.Pointer(pathCString)) + if link == nil { + return nil, fmt.Errorf("failed to attach u(ret)probe to program %s:%d with pid %d: %w ", path, offset, pid, errno) + } + + upType := Uprobe + if isUretprobe { + upType = Uretprobe + } + + bpfLink := &BPFLink{ + link: link, + prog: prog, + linkType: upType, + eventName: fmt.Sprintf("%s:%d:%d", path, pid, offset), + } + return bpfLink, nil +} + +// AttachGenericFD attaches the BPFProgram to a targetFd at the specified attachType hook. +func (p *BPFProg) AttachGenericFD(targetFd int, attachType BPFAttachType, flags AttachFlag) error { + progFd := C.bpf_program__fd(p.prog) + errC := C.bpf_prog_attach(progFd, C.int(targetFd), C.enum_bpf_attach_type(int(attachType)), C.uint(uint(flags))) + if errC < 0 { + return fmt.Errorf("failed to attach: %w", syscall.Errno(-errC)) + } + return nil +} + +// DetachGenericFD detaches the BPFProgram associated with the targetFd at the hook specified by attachType. +func (p *BPFProg) DetachGenericFD(targetFd int, attachType BPFAttachType) error { + progFd := C.bpf_program__fd(p.prog) + errC := C.bpf_prog_detach2(progFd, C.int(targetFd), C.enum_bpf_attach_type(int(attachType))) + if errC < 0 { + return fmt.Errorf("failed to detach: %w", syscall.Errno(-errC)) + } + return nil +} diff --git a/tchook-common.go b/tchook-common.go new file mode 100644 index 00000000..af06c90c --- /dev/null +++ b/tchook-common.go @@ -0,0 +1,30 @@ +package libbpfgo + +/* +#cgo LDFLAGS: -lelf -lz +#include "libbpfgo.h" +*/ +import "C" + +// +// TcAttachPoint +// + +type TcAttachPoint uint32 + +const ( + BPFTcIngress TcAttachPoint = C.BPF_TC_INGRESS + BPFTcEgress TcAttachPoint = C.BPF_TC_EGRESS + BPFTcIngressEgress TcAttachPoint = C.BPF_TC_INGRESS | C.BPF_TC_EGRESS + BPFTcCustom TcAttachPoint = C.BPF_TC_CUSTOM +) + +// +// TcFlags +// + +type TcFlags uint32 + +const ( + BpfTcFReplace TcFlags = C.BPF_TC_F_REPLACE +) diff --git a/tchook.go b/tchook.go new file mode 100644 index 00000000..c1384b1b --- /dev/null +++ b/tchook.go @@ -0,0 +1,133 @@ +package libbpfgo + +/* +#cgo LDFLAGS: -lelf -lz +#include "libbpfgo.h" +*/ +import "C" + +import ( + "fmt" + "net" + "syscall" +) + +// +// TcHook +// + +type TcHook struct { + hook *C.struct_bpf_tc_hook +} + +func (hook *TcHook) SetInterfaceByIndex(ifaceIdx int) { + hook.hook.ifindex = C.int(ifaceIdx) +} + +func (hook *TcHook) SetInterfaceByName(ifaceName string) error { + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + return err + } + hook.hook.ifindex = C.int(iface.Index) + + return nil +} + +func (hook *TcHook) GetInterfaceIndex() int { + return int(hook.hook.ifindex) +} + +func (hook *TcHook) SetAttachPoint(attachPoint TcAttachPoint) { + hook.hook.attach_point = uint32(attachPoint) +} + +func (hook *TcHook) SetParent(a int, b int) { + parent := (((a) << 16) & 0xFFFF0000) | ((b) & 0x0000FFFF) + hook.hook.parent = C.uint(parent) +} + +func (hook *TcHook) Create() error { + errC := C.bpf_tc_hook_create(hook.hook) + if errC < 0 { + return fmt.Errorf("failed to create tc hook: %w", syscall.Errno(-errC)) + } + + return nil +} + +func (hook *TcHook) Destroy() error { + errC := C.bpf_tc_hook_destroy(hook.hook) + if errC < 0 { + return fmt.Errorf("failed to destroy tc hook: %w", syscall.Errno(-errC)) + } + + return nil +} + +type TcOpts struct { + ProgFd int + Flags TcFlags + ProgId uint + Handle uint + Priority uint +} + +func tcOptsToC(tcOpts *TcOpts) *C.struct_bpf_tc_opts { + if tcOpts == nil { + return nil + } + opts := C.struct_bpf_tc_opts{} + opts.sz = C.sizeof_struct_bpf_tc_opts + opts.prog_fd = C.int(tcOpts.ProgFd) + opts.flags = C.uint(tcOpts.Flags) + opts.prog_id = C.uint(tcOpts.ProgId) + opts.handle = C.uint(tcOpts.Handle) + opts.priority = C.uint(tcOpts.Priority) + + return &opts +} + +func tcOptsFromC(tcOpts *TcOpts, opts *C.struct_bpf_tc_opts) { + if opts == nil { + return + } + tcOpts.ProgFd = int(opts.prog_fd) + tcOpts.Flags = TcFlags(opts.flags) + tcOpts.ProgId = uint(opts.prog_id) + tcOpts.Handle = uint(opts.handle) + tcOpts.Priority = uint(opts.priority) +} + +func (hook *TcHook) Attach(tcOpts *TcOpts) error { + opts := tcOptsToC(tcOpts) + errC := C.bpf_tc_attach(hook.hook, opts) + if errC < 0 { + return fmt.Errorf("failed to attach tc hook: %w", syscall.Errno(-errC)) + } + tcOptsFromC(tcOpts, opts) + + return nil +} + +func (hook *TcHook) Detach(tcOpts *TcOpts) error { + opts := tcOptsToC(tcOpts) + errC := C.bpf_tc_detach(hook.hook, opts) + if errC < 0 { + return fmt.Errorf("failed to detach tc hook: %w", syscall.Errno(-errC)) + } + tcOptsFromC(tcOpts, opts) + + return nil +} + +func (hook *TcHook) Query(tcOpts *TcOpts) error { + opts := tcOptsToC(tcOpts) + errC := C.bpf_tc_query(hook.hook, opts) + if errC < 0 { + return fmt.Errorf("failed to query tc hook: %w", syscall.Errno(-errC)) + } + tcOptsFromC(tcOpts, opts) + + return nil +} From 0c6a05f9e8a0e389e109f27a469a7d42dcfabcd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Fri, 25 Aug 2023 09:52:09 -0300 Subject: [PATCH 02/11] chore(link): make BPF link legacy a private type It has no methods, and is only used embedded in the BPFLink type. --- link.go | 10 +++------- prog.go | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/link.go b/link.go index 2b1276fe..67c4869d 100644 --- a/link.go +++ b/link.go @@ -36,24 +36,20 @@ const ( ) // -// BPFLinkLegacy +// BPFLink // -type BPFLinkLegacy struct { +type bpfLinkLegacy struct { attachType BPFAttachType cgroupDir string } -// -// BPFLink -// - type BPFLink struct { link *C.struct_bpf_link prog *BPFProg linkType LinkType eventName string - legacy *BPFLinkLegacy // if set, this is a fake BPFLink + legacy *bpfLinkLegacy // if set, this is a fake BPFLink } func (l *BPFLink) DestroyLegacy(linkType LinkType) error { diff --git a/prog.go b/prog.go index 267e4d61..6fe88e8e 100644 --- a/prog.go +++ b/prog.go @@ -214,7 +214,7 @@ func (p *BPFProg) AttachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttac } dirName := strings.ReplaceAll(cgroupV2DirPath[1:], "/", "-") - bpfLinkLegacy := &BPFLinkLegacy{ + bpfLinkLegacy := &bpfLinkLegacy{ attachType: attachType, cgroupDir: cgroupV2DirPath, } From a4c5f50fbd088a51ffa4b33172b8ec83ac616f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Fri, 25 Aug 2023 14:21:03 -0300 Subject: [PATCH 03/11] chore(go): rename C variables - Changed C function returns to 'retC' in functions that return error values to avoid confusion with possible use of errno. - Changed returns to 'valueC' in functions that return 'value' or pointers to 'value'. This also changed comparisons to "< 0" instead of "!= 0". --- buf-perf.go | 9 +- buf-ring.go | 9 +- libbpfgo.go | 24 +++--- link.go | 32 ++++---- module-iterator.go | 20 ++--- module.go | 92 ++++++++++----------- prog.go | 200 ++++++++++++++++++++++++--------------------- tchook.go | 12 +-- 8 files changed, 207 insertions(+), 191 deletions(-) diff --git a/buf-perf.go b/buf-perf.go index 7107db03..2bf1edfd 100644 --- a/buf-perf.go +++ b/buf-perf.go @@ -95,12 +95,13 @@ func (pb *PerfBuffer) poll(timeout int) error { case <-pb.stop: return nil default: - err := C.perf_buffer__poll(pb.pb, C.int(timeout)) - if err < 0 { - if syscall.Errno(-err) == syscall.EINTR { + retC := C.perf_buffer__poll(pb.pb, C.int(timeout)) + if retC < 0 { + errno := syscall.Errno(-retC) + if errno == syscall.EINTR { continue } - return fmt.Errorf("error polling perf buffer: %d", err) + return fmt.Errorf("error polling perf buffer: %w", errno) } } } diff --git a/buf-ring.go b/buf-ring.go index 29d68835..a4ee7d64 100644 --- a/buf-ring.go +++ b/buf-ring.go @@ -90,16 +90,17 @@ func (rb *RingBuffer) poll(timeout int) error { defer rb.wg.Done() for { - err := C.ring_buffer__poll(rb.rb, C.int(timeout)) + retC := C.ring_buffer__poll(rb.rb, C.int(timeout)) if rb.isStopped() { break } - if err < 0 { - if syscall.Errno(-err) == syscall.EINTR { + if retC < 0 { + errno := syscall.Errno(-retC) + if errno == syscall.EINTR { continue } - return fmt.Errorf("error polling ring buffer: %d", err) + return fmt.Errorf("error polling ring buffer: %w", errno) } } return nil diff --git a/libbpfgo.go b/libbpfgo.go index 4e686995..83415717 100644 --- a/libbpfgo.go +++ b/libbpfgo.go @@ -77,19 +77,19 @@ func SetStrictMode(mode LibbpfStrictMode) { // func BPFProgramTypeIsSupported(progType BPFProgType) (bool, error) { - cSupported := C.libbpf_probe_bpf_prog_type(C.enum_bpf_prog_type(int(progType)), nil) - if cSupported < 1 { - return false, syscall.Errno(-cSupported) + supportedC := C.libbpf_probe_bpf_prog_type(C.enum_bpf_prog_type(int(progType)), nil) + if supportedC < 1 { + return false, syscall.Errno(-supportedC) } - return cSupported == 1, nil + return supportedC == 1, nil } func BPFMapTypeIsSupported(mapType MapType) (bool, error) { - cSupported := C.libbpf_probe_bpf_map_type(C.enum_bpf_map_type(int(mapType)), nil) - if cSupported < 1 { - return false, syscall.Errno(-cSupported) + supportedC := C.libbpf_probe_bpf_map_type(C.enum_bpf_map_type(int(mapType)), nil) + if supportedC < 1 { + return false, syscall.Errno(-supportedC) } - return cSupported == 1, nil + return supportedC == 1, nil } // @@ -97,9 +97,9 @@ func BPFMapTypeIsSupported(mapType MapType) (bool, error) { // func NumPossibleCPUs() (int, error) { - numCPUs, errC := C.libbpf_num_possible_cpus() - if numCPUs < 0 { - return 0, fmt.Errorf("failed to retrieve the number of CPUs: %w", errC) + nCPUsC := C.libbpf_num_possible_cpus() + if nCPUsC < 0 { + return 0, fmt.Errorf("failed to retrieve the number of CPUs: %w", syscall.Errno(-nCPUsC)) } - return int(numCPUs), nil + return int(nCPUsC), nil } diff --git a/link.go b/link.go index 67c4869d..dfe84c85 100644 --- a/link.go +++ b/link.go @@ -67,8 +67,8 @@ func (l *BPFLink) Destroy() error { if l.legacy != nil { return l.DestroyLegacy(l.linkType) } - if ret := C.bpf_link__destroy(l.link); ret < 0 { - return syscall.Errno(-ret) + if retC := C.bpf_link__destroy(l.link); retC < 0 { + return syscall.Errno(-retC) } l.link = nil return nil @@ -84,21 +84,21 @@ func (l *BPFLink) GetFd() int { } func (l *BPFLink) Pin(pinPath string) error { - path := C.CString(pinPath) - errC := C.bpf_link__pin(l.link, path) - C.free(unsafe.Pointer(path)) - if errC != 0 { - return fmt.Errorf("failed to pin link %s to path %s: %w", l.eventName, pinPath, syscall.Errno(-errC)) + pathC := C.CString(pinPath) + retC := C.bpf_link__pin(l.link, pathC) + C.free(unsafe.Pointer(pathC)) + if retC < 0 { + return fmt.Errorf("failed to pin link %s to path %s: %w", l.eventName, pinPath, syscall.Errno(-retC)) } return nil } func (l *BPFLink) Unpin(pinPath string) error { - path := C.CString(pinPath) - errC := C.bpf_link__unpin(l.link) - C.free(unsafe.Pointer(path)) - if errC != 0 { - return fmt.Errorf("failed to unpin link %s from path %s: %w", l.eventName, pinPath, syscall.Errno(-errC)) + pathC := C.CString(pinPath) + retC := C.bpf_link__unpin(l.link) + C.free(unsafe.Pointer(pathC)) + if retC < 0 { + return fmt.Errorf("failed to unpin link %s from path %s: %w", l.eventName, pinPath, syscall.Errno(-retC)) } return nil } @@ -108,12 +108,12 @@ func (l *BPFLink) Unpin(pinPath string) error { // func (l *BPFLink) Reader() (*BPFLinkReader, error) { - fd, errno := C.bpf_iter_create(C.int(l.FileDescriptor())) - if fd < 0 { - return nil, fmt.Errorf("failed to create reader: %w", errno) + fdC := C.bpf_iter_create(C.int(l.FileDescriptor())) + if fdC < 0 { + return nil, fmt.Errorf("failed to create reader: %w", syscall.Errno(-fdC)) } return &BPFLinkReader{ l: l, - fd: int(uintptr(fd)), + fd: int(fdC), }, nil } diff --git a/module-iterator.go b/module-iterator.go index 94b6bd5b..29f80527 100644 --- a/module-iterator.go +++ b/module-iterator.go @@ -18,18 +18,18 @@ type BPFObjectIterator struct { } func (it *BPFObjectIterator) NextMap() *BPFMap { - var startMap *C.struct_bpf_map + var startMapC *C.struct_bpf_map if it.prevMap != nil && it.prevMap.bpfMap != nil { - startMap = it.prevMap.bpfMap + startMapC = it.prevMap.bpfMap } - m := C.bpf_object__next_map(it.m.obj, startMap) - if m == nil { + bpfMapC := C.bpf_object__next_map(it.m.obj, startMapC) + if bpfMapC == nil { return nil } bpfMap := &BPFMap{ - bpfMap: m, + bpfMap: bpfMapC, module: it.m, } it.prevMap = bpfMap @@ -63,15 +63,15 @@ func (it *BPFObjectIterator) NextProgram() *BPFProg { startProg = it.prevProg.prog } - p := C.bpf_object__next_program(it.m.obj, startProg) - if p == nil { + progC := C.bpf_object__next_program(it.m.obj, startProg) + if progC == nil { return nil } - cName := C.bpf_program__name(p) + nameC := C.bpf_program__name(progC) prog := &BPFProg{ - name: C.GoString(cName), - prog: p, + name: C.GoString(nameC), + prog: progC, module: it.m, } it.prevProg = prog diff --git a/module.go b/module.go index 2b67b58f..f52321ca 100644 --- a/module.go +++ b/module.go @@ -63,33 +63,33 @@ func NewModuleFromFileArgs(args NewModuleArgs) (*Module, error) { } } - opts := C.struct_bpf_object_open_opts{} - opts.sz = C.sizeof_struct_bpf_object_open_opts + optsC := C.struct_bpf_object_open_opts{} + optsC.sz = C.sizeof_struct_bpf_object_open_opts - bpfFile := C.CString(args.BPFObjPath) - defer C.free(unsafe.Pointer(bpfFile)) + bpfFileC := C.CString(args.BPFObjPath) + defer C.free(unsafe.Pointer(bpfFileC)) // instruct libbpf to use user provided kernel BTF file if args.BTFObjPath != "" { - btfFile := C.CString(args.BTFObjPath) - opts.btf_custom_path = btfFile - defer C.free(unsafe.Pointer(btfFile)) + btfFileC := C.CString(args.BTFObjPath) + optsC.btf_custom_path = btfFileC + defer C.free(unsafe.Pointer(btfFileC)) } // instruct libbpf to use user provided KConfigFile if args.KConfigFilePath != "" { - kConfigFile := C.CString(args.KConfigFilePath) - opts.kconfig = kConfigFile - defer C.free(unsafe.Pointer(kConfigFile)) + kConfigFileC := C.CString(args.KConfigFilePath) + optsC.kconfig = kConfigFileC + defer C.free(unsafe.Pointer(kConfigFileC)) } - obj, errno := C.bpf_object__open_file(bpfFile, &opts) - if obj == nil { + objC, errno := C.bpf_object__open_file(bpfFileC, &optsC) + if objC == nil { return nil, fmt.Errorf("failed to open BPF object at path %s: %w", args.BPFObjPath, errno) } return &Module{ - obj: obj, + obj: objC, elf: f, }, nil } @@ -117,33 +117,33 @@ func NewModuleFromBufferArgs(args NewModuleArgs) (*Module, error) { args.BTFObjPath = "/sys/kernel/btf/vmlinux" } - cBTFFilePath := C.CString(args.BTFObjPath) - defer C.free(unsafe.Pointer(cBTFFilePath)) - cKconfigPath := C.CString(args.KConfigFilePath) - defer C.free(unsafe.Pointer(cKconfigPath)) - cBPFObjName := C.CString(args.BPFObjName) - defer C.free(unsafe.Pointer(cBPFObjName)) - cBPFBuff := unsafe.Pointer(C.CBytes(args.BPFObjBuff)) - defer C.free(cBPFBuff) - cBPFBuffSize := C.size_t(len(args.BPFObjBuff)) + btfFilePathC := C.CString(args.BTFObjPath) + defer C.free(unsafe.Pointer(btfFilePathC)) + kConfigPathC := C.CString(args.KConfigFilePath) + defer C.free(unsafe.Pointer(kConfigPathC)) + bpfObjNameC := C.CString(args.BPFObjName) + defer C.free(unsafe.Pointer(bpfObjNameC)) + bpfBuffC := unsafe.Pointer(C.CBytes(args.BPFObjBuff)) + defer C.free(bpfBuffC) + bpfBuffSizeC := C.size_t(len(args.BPFObjBuff)) if len(args.KConfigFilePath) <= 2 { - cKconfigPath = nil + kConfigPathC = nil } - cOpts, errno := C.cgo_bpf_object_open_opts_new(cBTFFilePath, cKconfigPath, cBPFObjName) - if cOpts == nil { + optsC, errno := C.cgo_bpf_object_open_opts_new(btfFilePathC, kConfigPathC, bpfObjNameC) + if optsC == nil { return nil, fmt.Errorf("failed to create bpf_object_open_opts to %s: %w", args.BPFObjName, errno) } - defer C.cgo_bpf_object_open_opts_free(cOpts) + defer C.cgo_bpf_object_open_opts_free(optsC) - obj, errno := C.bpf_object__open_mem(cBPFBuff, cBPFBuffSize, cOpts) - if obj == nil { + objC, errno := C.bpf_object__open_mem(bpfBuffC, bpfBuffSizeC, optsC) + if objC == nil { return nil, fmt.Errorf("failed to open BPF object %s: %w", args.BPFObjName, errno) } return &Module{ - obj: obj, + obj: objC, elf: f, }, nil } @@ -182,9 +182,9 @@ func (m *Module) Close() { } func (m *Module) BPFLoadObject() error { - ret := C.bpf_object__load(m.obj) - if ret != 0 { - return fmt.Errorf("failed to load BPF object: %w", syscall.Errno(-ret)) + retC := C.bpf_object__load(m.obj) + if retC < 0 { + return fmt.Errorf("failed to load BPF object: %w", syscall.Errno(-retC)) } m.loaded = true m.elf.Close() @@ -234,9 +234,9 @@ func (m *Module) InitGlobalVariable(name string, value interface{}) error { } func (m *Module) GetMap(mapName string) (*BPFMap, error) { - cs := C.CString(mapName) - bpfMapC, errno := C.bpf_object__find_map_by_name(m.obj, cs) - C.free(unsafe.Pointer(cs)) + mapNameC := C.CString(mapName) + bpfMapC, errno := C.bpf_object__find_map_by_name(m.obj, mapNameC) + C.free(unsafe.Pointer(mapNameC)) if bpfMapC == nil { return nil, fmt.Errorf("failed to find BPF map %s: %w", mapName, errno) } @@ -297,16 +297,16 @@ func (m *Module) GetMap(mapName string) (*BPFMap, error) { } func (m *Module) GetProgram(progName string) (*BPFProg, error) { - cs := C.CString(progName) - prog, errno := C.bpf_object__find_program_by_name(m.obj, cs) - C.free(unsafe.Pointer(cs)) - if prog == nil { + progNameC := C.CString(progName) + progC, errno := C.bpf_object__find_program_by_name(m.obj, progNameC) + C.free(unsafe.Pointer(progNameC)) + if progC == nil { return nil, fmt.Errorf("failed to find BPF program %s: %w", progName, errno) } return &BPFProg{ name: progName, - prog: prog, + prog: progC, module: m, }, nil } @@ -326,13 +326,13 @@ func (m *Module) InitRingBuf(mapName string, eventsChan chan []byte) (*RingBuffe return nil, fmt.Errorf("max ring buffers reached") } - rb := C.cgo_init_ring_buf(C.int(bpfMap.FileDescriptor()), C.uintptr_t(slot)) - if rb == nil { + rbC := C.cgo_init_ring_buf(C.int(bpfMap.FileDescriptor()), C.uintptr_t(slot)) + if rbC == nil { return nil, fmt.Errorf("failed to initialize ring buffer") } ringBuf := &RingBuffer{ - rb: rb, + rb: rbC, bpfMap: bpfMap, slot: uint(slot), } @@ -360,13 +360,13 @@ func (m *Module) InitPerfBuf(mapName string, eventsChan chan []byte, lostChan ch return nil, fmt.Errorf("max number of ring/perf buffers reached") } - pb := C.cgo_init_perf_buf(C.int(bpfMap.FileDescriptor()), C.int(pageCnt), C.uintptr_t(slot)) - if pb == nil { + pbC := C.cgo_init_perf_buf(C.int(bpfMap.FileDescriptor()), C.int(pageCnt), C.uintptr_t(slot)) + if pbC == nil { eventChannels.remove(uint(slot)) return nil, fmt.Errorf("failed to initialize perf buffer") } - perfBuf.pb = pb + perfBuf.pb = pbC perfBuf.slot = uint(slot) m.perfBufs = append(m.perfBufs, perfBuf) diff --git a/prog.go b/prog.go index 6fe88e8e..356791bf 100644 --- a/prog.go +++ b/prog.go @@ -41,22 +41,22 @@ func (p *BPFProg) Pin(path string) error { return fmt.Errorf("invalid path: %s: %v", path, err) } - cs := C.CString(absPath) - ret := C.bpf_program__pin(p.prog, cs) - C.free(unsafe.Pointer(cs)) - if ret != 0 { - return fmt.Errorf("failed to pin program %s to %s: %w", p.name, path, syscall.Errno(-ret)) + absPathC := C.CString(absPath) + retC := C.bpf_program__pin(p.prog, absPathC) + C.free(unsafe.Pointer(absPathC)) + if retC < 0 { + return fmt.Errorf("failed to pin program %s to %s: %w", p.name, path, syscall.Errno(-retC)) } p.pinnedPath = absPath return nil } func (p *BPFProg) Unpin(path string) error { - cs := C.CString(path) - ret := C.bpf_program__unpin(p.prog, cs) - C.free(unsafe.Pointer(cs)) - if ret != 0 { - return fmt.Errorf("failed to unpin program %s to %s: %w", p.name, path, syscall.Errno(-ret)) + pathC := C.CString(path) + retC := C.bpf_program__unpin(p.prog, pathC) + C.free(unsafe.Pointer(pathC)) + if retC < 0 { + return fmt.Errorf("failed to unpin program %s to %s: %w", p.name, path, syscall.Errno(-retC)) } p.pinnedPath = "" return nil @@ -98,10 +98,9 @@ func (p *BPFProg) GetType() BPFProgType { } func (p *BPFProg) SetAutoload(autoload bool) error { - cbool := C.bool(autoload) - ret := C.bpf_program__set_autoload(p.prog, cbool) - if ret != 0 { - return fmt.Errorf("failed to set bpf program autoload: %w", syscall.Errno(-ret)) + retC := C.bpf_program__set_autoload(p.prog, C.bool(autoload)) + if retC < 0 { + return fmt.Errorf("failed to set bpf program autoload: %w", syscall.Errno(-retC)) } return nil } @@ -110,12 +109,12 @@ func (p *BPFProg) SetAutoload(autoload bool) error { // for the attach target. You can specify the destination in BPF code // via the SEC() such as `SEC("fentry/some_kernel_func")` func (p *BPFProg) AttachGeneric() (*BPFLink, error) { - link, errno := C.bpf_program__attach(p.prog) - if link == nil { + linkC, errno := C.bpf_program__attach(p.prog) + if linkC == nil { return nil, fmt.Errorf("failed to attach program: %w", errno) } bpfLink := &BPFLink{ - link: link, + link: linkC, prog: p, linkType: Tracing, eventName: fmt.Sprintf("tracing-%s", p.name), @@ -126,11 +125,11 @@ func (p *BPFProg) AttachGeneric() (*BPFLink, error) { // SetAttachTarget can be used to specify the program and/or function to attach // the BPF program to. To attach to a kernel function specify attachProgFD as 0 func (p *BPFProg) SetAttachTarget(attachProgFD int, attachFuncName string) error { - cs := C.CString(attachFuncName) - ret := C.bpf_program__set_attach_target(p.prog, C.int(attachProgFD), cs) - C.free(unsafe.Pointer(cs)) - if ret != 0 { - return fmt.Errorf("failed to set attach target for program %s %s %w", p.name, attachFuncName, syscall.Errno(-ret)) + attachFuncNameC := C.CString(attachFuncName) + retC := C.bpf_program__set_attach_target(p.prog, C.int(attachProgFD), attachFuncNameC) + C.free(unsafe.Pointer(attachFuncNameC)) + if retC < 0 { + return fmt.Errorf("failed to set attach target for program %s %s %w", p.name, attachFuncName, syscall.Errno(-retC)) } return nil } @@ -166,8 +165,8 @@ func (p *BPFProg) AttachCgroup(cgroupV2DirPath string) (*BPFLink, error) { } defer syscall.Close(cgroupDirFD) - link, errno := C.bpf_program__attach_cgroup(p.prog, C.int(cgroupDirFD)) - if link == nil { + linkC, errno := C.bpf_program__attach_cgroup(p.prog, C.int(cgroupDirFD)) + if linkC == nil { return nil, fmt.Errorf("failed to attach cgroup on cgroupv2 %s to program %s: %w", cgroupV2DirPath, p.name, errno) } @@ -178,7 +177,7 @@ func (p *BPFProg) AttachCgroup(cgroupV2DirPath string) (*BPFLink, error) { // to be cgroup-progName-sys-fs-cgroup-unified instead. dirName := strings.ReplaceAll(cgroupV2DirPath[1:], "/", "-") bpfLink := &BPFLink{ - link: link, + link: linkC, prog: p, linkType: Cgroup, eventName: fmt.Sprintf("cgroup-%s-%s", p.name, dirName), @@ -207,10 +206,13 @@ func (p *BPFProg) AttachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttac return nil, err } defer syscall.Close(cgroupDirFD) - progFD := C.bpf_program__fd(p.prog) - ret := C.cgo_bpf_prog_attach_cgroup_legacy(progFD, C.int(cgroupDirFD), C.int(attachType)) - if ret < 0 { - return nil, fmt.Errorf("failed to attach (legacy) program %s to cgroupv2 %s", p.name, cgroupV2DirPath) + retC, errno := C.cgo_bpf_prog_attach_cgroup_legacy( + C.int(p.FileDescriptor()), + C.int(cgroupDirFD), + C.int(attachType), + ) + if retC < 0 { + return nil, fmt.Errorf("failed to attach (legacy) program %s to cgroupv2 %s: %w", p.name, cgroupV2DirPath, errno) } dirName := strings.ReplaceAll(cgroupV2DirPath[1:], "/", "-") @@ -241,10 +243,13 @@ func (p *BPFProg) DetachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttac return err } defer syscall.Close(cgroupDirFD) - progFD := C.bpf_program__fd(p.prog) - ret := C.cgo_bpf_prog_detach_cgroup_legacy(progFD, C.int(cgroupDirFD), C.int(attachType)) - if ret < 0 { - return fmt.Errorf("failed to detach (legacy) program %s from cgroupv2 %s", p.name, cgroupV2DirPath) + retC, errno := C.cgo_bpf_prog_detach_cgroup_legacy( + C.int(p.FileDescriptor()), + C.int(cgroupDirFD), + C.int(attachType), + ) + if retC < 0 { + return fmt.Errorf("failed to detach (legacy) program %s from cgroupv2 %s: %w", p.name, cgroupV2DirPath, errno) } return nil } @@ -254,13 +259,13 @@ func (p *BPFProg) AttachXDP(deviceName string) (*BPFLink, error) { if err != nil { return nil, fmt.Errorf("failed to find device by name %s: %w", deviceName, err) } - link, errno := C.bpf_program__attach_xdp(p.prog, C.int(iface.Index)) - if link == nil { + linkC, errno := C.bpf_program__attach_xdp(p.prog, C.int(iface.Index)) + if linkC == nil { return nil, fmt.Errorf("failed to attach xdp on device %s to program %s: %w", deviceName, p.name, errno) } bpfLink := &BPFLink{ - link: link, + link: linkC, prog: p, linkType: XDP, eventName: fmt.Sprintf("xdp-%s-%s", p.name, deviceName), @@ -270,17 +275,17 @@ func (p *BPFProg) AttachXDP(deviceName string) (*BPFLink, error) { } func (p *BPFProg) AttachTracepoint(category, name string) (*BPFLink, error) { - tpCategory := C.CString(category) - tpName := C.CString(name) - link, errno := C.bpf_program__attach_tracepoint(p.prog, tpCategory, tpName) - C.free(unsafe.Pointer(tpCategory)) - C.free(unsafe.Pointer(tpName)) - if link == nil { + tpCategoryC := C.CString(category) + tpNameC := C.CString(name) + linkC, errno := C.bpf_program__attach_tracepoint(p.prog, tpCategoryC, tpNameC) + C.free(unsafe.Pointer(tpCategoryC)) + C.free(unsafe.Pointer(tpNameC)) + if linkC == nil { return nil, fmt.Errorf("failed to attach tracepoint %s to program %s: %w", name, p.name, errno) } bpfLink := &BPFLink{ - link: link, + link: linkC, prog: p, linkType: Tracepoint, eventName: name, @@ -290,15 +295,15 @@ func (p *BPFProg) AttachTracepoint(category, name string) (*BPFLink, error) { } func (p *BPFProg) AttachRawTracepoint(tpEvent string) (*BPFLink, error) { - cs := C.CString(tpEvent) - link, errno := C.bpf_program__attach_raw_tracepoint(p.prog, cs) - C.free(unsafe.Pointer(cs)) - if link == nil { + tpEventC := C.CString(tpEvent) + linkC, errno := C.bpf_program__attach_raw_tracepoint(p.prog, tpEventC) + C.free(unsafe.Pointer(tpEventC)) + if linkC == nil { return nil, fmt.Errorf("failed to attach raw tracepoint %s to program %s: %w", tpEvent, p.name, errno) } bpfLink := &BPFLink{ - link: link, + link: linkC, prog: p, linkType: RawTracepoint, eventName: tpEvent, @@ -308,13 +313,13 @@ func (p *BPFProg) AttachRawTracepoint(tpEvent string) (*BPFLink, error) { } func (p *BPFProg) AttachLSM() (*BPFLink, error) { - link, errno := C.bpf_program__attach_lsm(p.prog) - if link == nil { + linkC, errno := C.bpf_program__attach_lsm(p.prog) + if linkC == nil { return nil, fmt.Errorf("failed to attach lsm to program %s: %w", p.name, errno) } bpfLink := &BPFLink{ - link: link, + link: linkC, prog: p, linkType: LSM, } @@ -323,13 +328,13 @@ func (p *BPFProg) AttachLSM() (*BPFLink, error) { } func (p *BPFProg) AttachPerfEvent(fd int) (*BPFLink, error) { - link, errno := C.bpf_program__attach_perf_event(p.prog, C.int(fd)) - if link == nil { + linkC, errno := C.bpf_program__attach_perf_event(p.prog, C.int(fd)) + if linkC == nil { return nil, fmt.Errorf("failed to attach perf event to program %s: %w", p.name, errno) } bpfLink := &BPFLink{ - link: link, + link: linkC, prog: p, linkType: PerfEvent, } @@ -348,11 +353,10 @@ func (p *BPFProg) AttachKretprobe(kp string) (*BPFLink, error) { } func doAttachKprobe(prog *BPFProg, kp string, isKretprobe bool) (*BPFLink, error) { - cs := C.CString(kp) - cbool := C.bool(isKretprobe) - link, errno := C.bpf_program__attach_kprobe(prog.prog, cbool, cs) - C.free(unsafe.Pointer(cs)) - if link == nil { + kpC := C.CString(kp) + linkC, errno := C.bpf_program__attach_kprobe(prog.prog, C.bool(isKretprobe), kpC) + C.free(unsafe.Pointer(kpC)) + if linkC == nil { return nil, fmt.Errorf("failed to attach %s k(ret)probe to program %s: %w", kp, prog.name, errno) } @@ -362,7 +366,7 @@ func doAttachKprobe(prog *BPFProg, kp string, isKretprobe bool) (*BPFLink, error } bpfLink := &BPFLink{ - link: link, + link: linkC, prog: prog, linkType: kpType, eventName: kp, @@ -376,8 +380,8 @@ func (p *BPFProg) AttachNetns(networkNamespacePath string) (*BPFLink, error) { if fd < 0 { return nil, fmt.Errorf("failed to open network namespace path %s: %w", networkNamespacePath, err) } - link, errno := C.bpf_program__attach_netns(p.prog, C.int(fd)) - if link == nil { + linkC, errno := C.bpf_program__attach_netns(p.prog, C.int(fd)) + if linkC == nil { return nil, fmt.Errorf("failed to attach network namespace on %s to program %s: %w", networkNamespacePath, p.name, errno) } @@ -388,7 +392,7 @@ func (p *BPFProg) AttachNetns(networkNamespacePath string) (*BPFLink, error) { // to be netns-progName-proc-self-ns-net instead. fileName := strings.ReplaceAll(networkNamespacePath[1:], "/", "-") bpfLink := &BPFLink{ - link: link, + link: linkC, prog: p, linkType: Netns, eventName: fmt.Sprintf("netns-%s-%s", p.name, fileName), @@ -408,26 +412,27 @@ type IterOpts struct { } func (p *BPFProg) AttachIter(opts IterOpts) (*BPFLink, error) { - mapFd := C.uint(opts.MapFd) - cgroupIterOrder := uint32(opts.CgroupIterOrder) - cgroupFd := C.uint(opts.CgroupFd) - cgroupId := C.ulonglong(opts.CgroupId) - tid := C.uint(opts.Tid) - pid := C.uint(opts.Pid) - pidFd := C.uint(opts.PidFd) - cOpts, errno := C.cgo_bpf_iter_attach_opts_new(mapFd, cgroupIterOrder, cgroupFd, cgroupId, tid, pid, pidFd) - if cOpts == nil { + optsC, errno := C.cgo_bpf_iter_attach_opts_new( + C.uint(opts.MapFd), + uint32(opts.CgroupIterOrder), + C.uint(opts.CgroupFd), + C.ulonglong(opts.CgroupId), + C.uint(opts.Tid), + C.uint(opts.Pid), + C.uint(opts.PidFd), + ) + if optsC == nil { return nil, fmt.Errorf("failed to create iter_attach_opts to program %s: %w", p.name, errno) } - defer C.cgo_bpf_iter_attach_opts_free(cOpts) + defer C.cgo_bpf_iter_attach_opts_free(optsC) - link, errno := C.bpf_program__attach_iter(p.prog, cOpts) - if link == nil { + linkC, errno := C.bpf_program__attach_iter(p.prog, optsC) + if linkC == nil { return nil, fmt.Errorf("failed to attach iter to program %s: %w", p.name, errno) } eventName := fmt.Sprintf("iter-%s-%d", p.name, opts.MapFd) bpfLink := &BPFLink{ - link: link, + link: linkC, prog: p, linkType: Iter, eventName: eventName, @@ -461,14 +466,16 @@ func (p *BPFProg) AttachURetprobe(pid int, path string, offset uint32) (*BPFLink } func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offset uint32) (*BPFLink, error) { - retCBool := C.bool(isUretprobe) - pidCint := C.int(pid) - pathCString := C.CString(path) - offsetCsizet := C.size_t(offset) - - link, errno := C.bpf_program__attach_uprobe(prog.prog, retCBool, pidCint, pathCString, offsetCsizet) - C.free(unsafe.Pointer(pathCString)) - if link == nil { + pathC := C.CString(path) + linkC, errno := C.bpf_program__attach_uprobe( + prog.prog, + C.bool(isUretprobe), + C.int(pid), + pathC, + C.size_t(offset), + ) + C.free(unsafe.Pointer(pathC)) + if linkC == nil { return nil, fmt.Errorf("failed to attach u(ret)probe to program %s:%d with pid %d: %w ", path, offset, pid, errno) } @@ -478,7 +485,7 @@ func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offse } bpfLink := &BPFLink{ - link: link, + link: linkC, prog: prog, linkType: upType, eventName: fmt.Sprintf("%s:%d:%d", path, pid, offset), @@ -488,20 +495,27 @@ func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offse // AttachGenericFD attaches the BPFProgram to a targetFd at the specified attachType hook. func (p *BPFProg) AttachGenericFD(targetFd int, attachType BPFAttachType, flags AttachFlag) error { - progFd := C.bpf_program__fd(p.prog) - errC := C.bpf_prog_attach(progFd, C.int(targetFd), C.enum_bpf_attach_type(int(attachType)), C.uint(uint(flags))) - if errC < 0 { - return fmt.Errorf("failed to attach: %w", syscall.Errno(-errC)) + retC := C.bpf_prog_attach( + C.int(p.FileDescriptor()), + C.int(targetFd), + C.enum_bpf_attach_type(int(attachType)), + C.uint(uint(flags)), + ) + if retC < 0 { + return fmt.Errorf("failed to attach: %w", syscall.Errno(-retC)) } return nil } // DetachGenericFD detaches the BPFProgram associated with the targetFd at the hook specified by attachType. func (p *BPFProg) DetachGenericFD(targetFd int, attachType BPFAttachType) error { - progFd := C.bpf_program__fd(p.prog) - errC := C.bpf_prog_detach2(progFd, C.int(targetFd), C.enum_bpf_attach_type(int(attachType))) - if errC < 0 { - return fmt.Errorf("failed to detach: %w", syscall.Errno(-errC)) + retC := C.bpf_prog_detach2( + C.int(p.FileDescriptor()), + C.int(targetFd), + C.enum_bpf_attach_type(int(attachType)), + ) + if retC < 0 { + return fmt.Errorf("failed to detach: %w", syscall.Errno(-retC)) } return nil } diff --git a/tchook.go b/tchook.go index c1384b1b..ed3cc57c 100644 --- a/tchook.go +++ b/tchook.go @@ -48,18 +48,18 @@ func (hook *TcHook) SetParent(a int, b int) { } func (hook *TcHook) Create() error { - errC := C.bpf_tc_hook_create(hook.hook) - if errC < 0 { - return fmt.Errorf("failed to create tc hook: %w", syscall.Errno(-errC)) + retC := C.bpf_tc_hook_create(hook.hook) + if retC < 0 { + return fmt.Errorf("failed to create tc hook: %w", syscall.Errno(-retC)) } return nil } func (hook *TcHook) Destroy() error { - errC := C.bpf_tc_hook_destroy(hook.hook) - if errC < 0 { - return fmt.Errorf("failed to destroy tc hook: %w", syscall.Errno(-errC)) + retC := C.bpf_tc_hook_destroy(hook.hook) + if retC < 0 { + return fmt.Errorf("failed to destroy tc hook: %w", syscall.Errno(-retC)) } return nil From 913d1ea23e28124cdca8914d0530b018706898fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Fri, 25 Aug 2023 18:27:45 -0300 Subject: [PATCH 04/11] chore: use cgo_* helpers for struct lifecycle Introduced struct helpers to manage the C struct lifecycle on the C side, which avoids problems with structs which may contain bitfields. See #244. - cgo_bpf_map_info_new() - cgo_bpf_map_info_size() - cgo_bpf_map_info_free() - cgo_bpf_tc_opts_new() - cgo_bpf_tc_opts_free() - cgo_bpf_tc_hook_new() - cgo_bpf_tc_hook_free() Based on the same #244 concerns, introduced bpf_map_info getters: - cgo_bpf_map_info_type() - cgo_bpf_map_info_id() - cgo_bpf_map_info_key_size() - cgo_bpf_map_info_value_size() - cgo_bpf_map_info_max_entries() - cgo_bpf_map_info_map_flags() - cgo_bpf_map_info_name() - cgo_bpf_map_info_ifindex() - cgo_bpf_map_info_btf_vmlinux_value_type_id() - cgo_bpf_map_info_netns_dev() - cgo_bpf_map_info_netns_ino() - cgo_bpf_map_info_btf_id() - cgo_bpf_map_info_btf_key_type_id() - cgo_bpf_map_info_btf_value_type_id() - cgo_bpf_map_info_map_extra() - cgo_bpf_tc_opts_prog_fd() - cgo_bpf_tc_opts_flags() - cgo_bpf_tc_opts_prog_id() - cgo_bpf_tc_opts_handle() - cgo_bpf_tc_opts_priority() Changed these functions to use cgo_* struct handlers: - NewModuleFromFileArgs() - GetMapInfoByFD() - TcHook.Destroy() - TcHook.Attach() - TcHook.Detach() - TcHook.Query() --- libbpfgo.c | 232 ++++++++++++++++++++++++++++++++++++++++++++ libbpfgo.h | 41 ++++++++ map-common.go | 37 +++---- module.go | 36 +++---- selftest/tc/main.go | 6 ++ tchook.go | 94 +++++++++++------- 6 files changed, 377 insertions(+), 69 deletions(-) diff --git a/libbpfgo.c b/libbpfgo.c index 8efebdfd..67cd88eb 100644 --- a/libbpfgo.c +++ b/libbpfgo.c @@ -117,6 +117,10 @@ int cgo_bpf_prog_detach_cgroup_legacy(int prog_fd, // eBPF program file descri return syscall(__NR_bpf, BPF_PROG_DETACH, &attr, sizeof(attr)); } +// +// struct handlers +// + struct bpf_iter_attach_opts *cgo_bpf_iter_attach_opts_new(__u32 map_fd, enum bpf_cgroup_iter_order order, __u32 cgroup_fd, @@ -235,3 +239,231 @@ void cgo_bpf_map_batch_opts_free(struct bpf_map_batch_opts *opts) { free(opts); } + +struct bpf_map_info *cgo_bpf_map_info_new() +{ + struct bpf_map_info *info; + info = calloc(1, sizeof(*info)); + if (!info) + return NULL; + + return info; +} + +__u32 cgo_bpf_map_info_size() +{ + return sizeof(struct bpf_map_info); +} + +void cgo_bpf_map_info_free(struct bpf_map_info *info) +{ + free(info); +} + +struct bpf_tc_opts *cgo_bpf_tc_opts_new( + int prog_fd, __u32 flags, __u32 prog_id, __u32 handle, __u32 priority) +{ + struct bpf_tc_opts *opts; + opts = calloc(1, sizeof(*opts)); + if (!opts) + return NULL; + + opts->sz = sizeof(*opts); + opts->prog_fd = prog_fd; + opts->flags = flags; + opts->prog_id = prog_id; + opts->handle = handle; + opts->priority = priority; + + return opts; +} + +void cgo_bpf_tc_opts_free(struct bpf_tc_opts *opts) +{ + free(opts); +} + +struct bpf_tc_hook *cgo_bpf_tc_hook_new() +{ + struct bpf_tc_hook *hook; + hook = calloc(1, sizeof(*hook)); + if (!hook) + return NULL; + + hook->sz = sizeof(*hook); + + return hook; +} + +void cgo_bpf_tc_hook_free(struct bpf_tc_hook *hook) +{ + free(hook); +} + +// +// struct getters +// + +// bpf_map_info + +__u32 cgo_bpf_map_info_type(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->type; +} + +__u32 cgo_bpf_map_info_id(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->id; +} + +__u32 cgo_bpf_map_info_key_size(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->key_size; +} + +__u32 cgo_bpf_map_info_value_size(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->value_size; +} + +__u32 cgo_bpf_map_info_max_entries(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->max_entries; +} + +__u32 cgo_bpf_map_info_map_flags(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->map_flags; +} + +char *cgo_bpf_map_info_name(struct bpf_map_info *info) +{ + if (!info) + return NULL; + + return info->name; +} + +__u32 cgo_bpf_map_info_ifindex(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->ifindex; +} + +__u32 cgo_bpf_map_info_btf_vmlinux_value_type_id(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->btf_vmlinux_value_type_id; +} + +__u64 cgo_bpf_map_info_netns_dev(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->netns_dev; +} + +__u64 cgo_bpf_map_info_netns_ino(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->netns_ino; +} + +__u32 cgo_bpf_map_info_btf_id(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->btf_id; +} + +__u32 cgo_bpf_map_info_btf_key_type_id(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->btf_key_type_id; +} + +__u32 cgo_bpf_map_info_btf_value_type_id(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->btf_value_type_id; +} + +__u64 cgo_bpf_map_info_map_extra(struct bpf_map_info *info) +{ + if (!info) + return 0; + + return info->map_extra; +} + +// bpf_tc_opts + +int cgo_bpf_tc_opts_prog_fd(struct bpf_tc_opts *opts) +{ + if (!opts) + return 0; + + return opts->prog_fd; +} + +__u32 cgo_bpf_tc_opts_flags(struct bpf_tc_opts *opts) +{ + if (!opts) + return 0; + + return opts->flags; +} + +__u32 cgo_bpf_tc_opts_prog_id(struct bpf_tc_opts *opts) +{ + if (!opts) + return 0; + + return opts->prog_id; +} + +__u32 cgo_bpf_tc_opts_handle(struct bpf_tc_opts *opts) +{ + if (!opts) + return 0; + + return opts->handle; +} + +__u32 cgo_bpf_tc_opts_priority(struct bpf_tc_opts *opts) +{ + if (!opts) + return 0; + + return opts->priority; +} diff --git a/libbpfgo.h b/libbpfgo.h index 4bb30c64..b6feba26 100644 --- a/libbpfgo.h +++ b/libbpfgo.h @@ -60,4 +60,45 @@ void cgo_bpf_map_create_opts_free(struct bpf_map_create_opts *opts); struct bpf_map_batch_opts *cgo_bpf_map_batch_opts_new(__u64 elem_flags, __u64 flags); void cgo_bpf_map_batch_opts_free(struct bpf_map_batch_opts *opts); +struct bpf_map_info *cgo_bpf_map_info_new(); +__u32 cgo_bpf_map_info_size(); +void cgo_bpf_map_info_free(struct bpf_map_info *info); + +struct bpf_tc_opts *cgo_bpf_tc_opts_new( + int prog_fd, __u32 flags, __u32 prog_id, __u32 handle, __u32 priority); +void cgo_bpf_tc_opts_free(struct bpf_tc_opts *opts); + +struct bpf_tc_hook *cgo_bpf_tc_hook_new(); +void cgo_bpf_tc_hook_free(struct bpf_tc_hook *hook); + +// +// struct getters +// + +// bpf_map_info + +__u32 cgo_bpf_map_info_type(struct bpf_map_info *info); +__u32 cgo_bpf_map_info_id(struct bpf_map_info *info); +__u32 cgo_bpf_map_info_key_size(struct bpf_map_info *info); +__u32 cgo_bpf_map_info_value_size(struct bpf_map_info *info); +__u32 cgo_bpf_map_info_max_entries(struct bpf_map_info *info); +__u32 cgo_bpf_map_info_map_flags(struct bpf_map_info *info); +char *cgo_bpf_map_info_name(struct bpf_map_info *info); +__u32 cgo_bpf_map_info_ifindex(struct bpf_map_info *info); +__u32 cgo_bpf_map_info_btf_vmlinux_value_type_id(struct bpf_map_info *info); +__u64 cgo_bpf_map_info_netns_dev(struct bpf_map_info *info); +__u64 cgo_bpf_map_info_netns_ino(struct bpf_map_info *info); +__u32 cgo_bpf_map_info_btf_id(struct bpf_map_info *info); +__u32 cgo_bpf_map_info_btf_key_type_id(struct bpf_map_info *info); +__u32 cgo_bpf_map_info_btf_value_type_id(struct bpf_map_info *info); +__u64 cgo_bpf_map_info_map_extra(struct bpf_map_info *info); + +// bpf_tc_opts + +int cgo_bpf_tc_opts_prog_fd(struct bpf_tc_opts *opts); +__u32 cgo_bpf_tc_opts_flags(struct bpf_tc_opts *opts); +__u32 cgo_bpf_tc_opts_prog_id(struct bpf_tc_opts *opts); +__u32 cgo_bpf_tc_opts_handle(struct bpf_tc_opts *opts); +__u32 cgo_bpf_tc_opts_priority(struct bpf_tc_opts *opts); + #endif diff --git a/map-common.go b/map-common.go index 73062833..f912f3d4 100644 --- a/map-common.go +++ b/map-common.go @@ -137,30 +137,31 @@ func GetMapFDByID(id uint32) (int, error) { // GetMapInfoByFD returns the BPFMapInfo for the map with the given file descriptor. func GetMapInfoByFD(fd int) (*BPFMapInfo, error) { - var info C.struct_bpf_map_info - var infoLen C.uint = C.uint(C.sizeof_struct_bpf_map_info) + infoC := C.cgo_bpf_map_info_new() + defer C.cgo_bpf_map_info_free(infoC) - retC := C.bpf_map_get_info_by_fd(C.int(fd), &info, &infoLen) + infoLenC := C.cgo_bpf_map_info_size() + retC := C.bpf_map_get_info_by_fd(C.int(fd), infoC, &infoLenC) if retC < 0 { return nil, fmt.Errorf("failed to get map info for fd %d: %w", fd, syscall.Errno(-retC)) } return &BPFMapInfo{ - Type: MapType(uint32(info._type)), - ID: uint32(info.id), - KeySize: uint32(info.key_size), - ValueSize: uint32(info.value_size), - MaxEntries: uint32(info.max_entries), - MapFlags: uint32(info.map_flags), - Name: C.GoString(&info.name[0]), - IfIndex: uint32(info.ifindex), - BTFVmlinuxValueTypeID: uint32(info.btf_vmlinux_value_type_id), - NetnsDev: uint64(info.netns_dev), - NetnsIno: uint64(info.netns_ino), - BTFID: uint32(info.btf_id), - BTFKeyTypeID: uint32(info.btf_key_type_id), - BTFValueTypeID: uint32(info.btf_value_type_id), - MapExtra: uint64(info.map_extra), + Type: MapType(C.cgo_bpf_map_info_type(infoC)), + ID: uint32(C.cgo_bpf_map_info_id(infoC)), + KeySize: uint32(C.cgo_bpf_map_info_key_size(infoC)), + ValueSize: uint32(C.cgo_bpf_map_info_value_size(infoC)), + MaxEntries: uint32(C.cgo_bpf_map_info_max_entries(infoC)), + MapFlags: uint32(C.cgo_bpf_map_info_map_flags(infoC)), + Name: C.GoString(C.cgo_bpf_map_info_name(infoC)), + IfIndex: uint32(C.cgo_bpf_map_info_ifindex(infoC)), + BTFVmlinuxValueTypeID: uint32(C.cgo_bpf_map_info_btf_vmlinux_value_type_id(infoC)), + NetnsDev: uint64(C.cgo_bpf_map_info_netns_dev(infoC)), + NetnsIno: uint64(C.cgo_bpf_map_info_netns_ino(infoC)), + BTFID: uint32(C.cgo_bpf_map_info_btf_id(infoC)), + BTFKeyTypeID: uint32(C.cgo_bpf_map_info_btf_key_type_id(infoC)), + BTFValueTypeID: uint32(C.cgo_bpf_map_info_btf_value_type_id(infoC)), + MapExtra: uint64(C.cgo_bpf_map_info_map_extra(infoC)), }, nil } diff --git a/module.go b/module.go index f52321ca..bb5001a3 100644 --- a/module.go +++ b/module.go @@ -63,27 +63,30 @@ func NewModuleFromFileArgs(args NewModuleArgs) (*Module, error) { } } - optsC := C.struct_bpf_object_open_opts{} - optsC.sz = C.sizeof_struct_bpf_object_open_opts - - bpfFileC := C.CString(args.BPFObjPath) - defer C.free(unsafe.Pointer(bpfFileC)) + var btfFilePathC *C.char + var kconfigPathC *C.char // instruct libbpf to use user provided kernel BTF file if args.BTFObjPath != "" { - btfFileC := C.CString(args.BTFObjPath) - optsC.btf_custom_path = btfFileC - defer C.free(unsafe.Pointer(btfFileC)) + btfFilePathC = C.CString(args.BTFObjPath) + defer C.free(unsafe.Pointer(btfFilePathC)) } - // instruct libbpf to use user provided KConfigFile if args.KConfigFilePath != "" { - kConfigFileC := C.CString(args.KConfigFilePath) - optsC.kconfig = kConfigFileC - defer C.free(unsafe.Pointer(kConfigFileC)) + kconfigPathC = C.CString(args.KConfigFilePath) + defer C.free(unsafe.Pointer(kconfigPathC)) + } + + optsC, errno := C.cgo_bpf_object_open_opts_new(btfFilePathC, kconfigPathC, nil) + if optsC == nil { + return nil, fmt.Errorf("failed to create bpf_object_open_opts: %w", errno) } + defer C.cgo_bpf_object_open_opts_free(optsC) + + bpfFileC := C.CString(args.BPFObjPath) + defer C.free(unsafe.Pointer(bpfFileC)) - objC, errno := C.bpf_object__open_file(bpfFileC, &optsC) + objC, errno := C.bpf_object__open_file(bpfFileC, optsC) if objC == nil { return nil, fmt.Errorf("failed to open BPF object at path %s: %w", args.BPFObjPath, errno) } @@ -133,7 +136,7 @@ func NewModuleFromBufferArgs(args NewModuleArgs) (*Module, error) { optsC, errno := C.cgo_bpf_object_open_opts_new(btfFilePathC, kConfigPathC, bpfObjNameC) if optsC == nil { - return nil, fmt.Errorf("failed to create bpf_object_open_opts to %s: %w", args.BPFObjName, errno) + return nil, fmt.Errorf("failed to create bpf_object_open_opts: %w", errno) } defer C.cgo_bpf_object_open_opts_free(optsC) @@ -374,11 +377,8 @@ func (m *Module) InitPerfBuf(mapName string, eventsChan chan []byte, lostChan ch } func (m *Module) TcHookInit() *TcHook { - hook := C.struct_bpf_tc_hook{} - hook.sz = C.sizeof_struct_bpf_tc_hook - return &TcHook{ - hook: &hook, + hook: C.cgo_bpf_tc_hook_new(), } } diff --git a/selftest/tc/main.go b/selftest/tc/main.go index 07d51e72..0174eb16 100644 --- a/selftest/tc/main.go +++ b/selftest/tc/main.go @@ -28,6 +28,12 @@ func main() { } hook := bpfModule.TcHookInit() + defer func() { + if err := hook.Destroy(); err != nil { + fmt.Fprintln(os.Stderr, err) + } + }() + err = hook.SetInterfaceByName("lo") if err != nil { fmt.Fprintln(os.Stderr, "failed to set tc hook on interface lo: %v", err) diff --git a/tchook.go b/tchook.go index ed3cc57c..a30366b8 100644 --- a/tchook.go +++ b/tchook.go @@ -62,6 +62,8 @@ func (hook *TcHook) Destroy() error { return fmt.Errorf("failed to destroy tc hook: %w", syscall.Errno(-retC)) } + C.cgo_bpf_tc_hook_free(hook.hook) + return nil } @@ -73,61 +75,87 @@ type TcOpts struct { Priority uint } -func tcOptsToC(tcOpts *TcOpts) *C.struct_bpf_tc_opts { +func tcOptsToC(tcOpts *TcOpts) (*C.struct_bpf_tc_opts, error) { if tcOpts == nil { - return nil + return nil, nil + } + + optsC, errno := C.cgo_bpf_tc_opts_new( + C.int(tcOpts.ProgFd), + C.uint(tcOpts.Flags), + C.uint(tcOpts.ProgId), + C.uint(tcOpts.Handle), + C.uint(tcOpts.Priority), + ) + if optsC == nil { + return nil, fmt.Errorf("failed to create bpf_tc_opts: %w", errno) } - opts := C.struct_bpf_tc_opts{} - opts.sz = C.sizeof_struct_bpf_tc_opts - opts.prog_fd = C.int(tcOpts.ProgFd) - opts.flags = C.uint(tcOpts.Flags) - opts.prog_id = C.uint(tcOpts.ProgId) - opts.handle = C.uint(tcOpts.Handle) - opts.priority = C.uint(tcOpts.Priority) - - return &opts + + return optsC, nil } -func tcOptsFromC(tcOpts *TcOpts, opts *C.struct_bpf_tc_opts) { - if opts == nil { +func tcOptsFromC(tcOpts *TcOpts, optsC *C.struct_bpf_tc_opts) { + if optsC == nil { return } - tcOpts.ProgFd = int(opts.prog_fd) - tcOpts.Flags = TcFlags(opts.flags) - tcOpts.ProgId = uint(opts.prog_id) - tcOpts.Handle = uint(opts.handle) - tcOpts.Priority = uint(opts.priority) + + tcOpts.ProgFd = int(C.cgo_bpf_tc_opts_prog_fd(optsC)) + tcOpts.Flags = TcFlags(C.cgo_bpf_tc_opts_flags(optsC)) + tcOpts.ProgId = uint(C.cgo_bpf_tc_opts_prog_id(optsC)) + tcOpts.Handle = uint(C.cgo_bpf_tc_opts_handle(optsC)) + tcOpts.Priority = uint(C.cgo_bpf_tc_opts_priority(optsC)) } func (hook *TcHook) Attach(tcOpts *TcOpts) error { - opts := tcOptsToC(tcOpts) - errC := C.bpf_tc_attach(hook.hook, opts) - if errC < 0 { - return fmt.Errorf("failed to attach tc hook: %w", syscall.Errno(-errC)) + optsC, err := tcOptsToC(tcOpts) + if err != nil { + return err + } + defer C.cgo_bpf_tc_opts_free(optsC) + + retC := C.bpf_tc_attach(hook.hook, optsC) + if retC < 0 { + return fmt.Errorf("failed to attach tc hook: %w", syscall.Errno(-retC)) } - tcOptsFromC(tcOpts, opts) + + // update tcOpts with the values from the libbpf + tcOptsFromC(tcOpts, optsC) return nil } func (hook *TcHook) Detach(tcOpts *TcOpts) error { - opts := tcOptsToC(tcOpts) - errC := C.bpf_tc_detach(hook.hook, opts) - if errC < 0 { - return fmt.Errorf("failed to detach tc hook: %w", syscall.Errno(-errC)) + optsC, err := tcOptsToC(tcOpts) + if err != nil { + return err + } + defer C.cgo_bpf_tc_opts_free(optsC) + + retC := C.bpf_tc_detach(hook.hook, optsC) + if retC < 0 { + return fmt.Errorf("failed to detach tc hook: %w", syscall.Errno(-retC)) } - tcOptsFromC(tcOpts, opts) + + // update tcOpts with the values from the libbpf + tcOptsFromC(tcOpts, optsC) return nil } func (hook *TcHook) Query(tcOpts *TcOpts) error { - opts := tcOptsToC(tcOpts) - errC := C.bpf_tc_query(hook.hook, opts) - if errC < 0 { - return fmt.Errorf("failed to query tc hook: %w", syscall.Errno(-errC)) + optsC, err := tcOptsToC(tcOpts) + if err != nil { + return err } - tcOptsFromC(tcOpts, opts) + defer C.cgo_bpf_tc_opts_free(optsC) + + retC := C.bpf_tc_query(hook.hook, optsC) + if retC < 0 { + return fmt.Errorf("failed to query tc hook: %w", syscall.Errno(-retC)) + } + + // update tcOpts with the values from the libbpf + tcOptsFromC(tcOpts, optsC) return nil } From 1b882cfd2be6999e5ad31ebac3811d9effef5f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Fri, 25 Aug 2023 18:33:16 -0300 Subject: [PATCH 05/11] chore: add information to SetStrictMode() SetStrictMode is no-op as of libbpf v1.0. --- libbpfgo.go | 1 + 1 file changed, 1 insertion(+) diff --git a/libbpfgo.go b/libbpfgo.go index 83415717..62a68d77 100644 --- a/libbpfgo.go +++ b/libbpfgo.go @@ -68,6 +68,7 @@ func (b LibbpfStrictMode) String() (str string) { return str } +// SetStrictMode is no-op as of libbpf v1.0 func SetStrictMode(mode LibbpfStrictMode) { C.libbpf_set_strict_mode(uint32(mode)) } From 3aeb45bbef296ec0e285bfa261b0c096b7b19167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 28 Aug 2023 10:13:56 -0300 Subject: [PATCH 06/11] fix(link)!: Unpin link API The underlying bpf_link__unpin() does not receive a pointer to the pin path, only for the link itself. --- link.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/link.go b/link.go index dfe84c85..d93f75d7 100644 --- a/link.go +++ b/link.go @@ -93,12 +93,10 @@ func (l *BPFLink) Pin(pinPath string) error { return nil } -func (l *BPFLink) Unpin(pinPath string) error { - pathC := C.CString(pinPath) +func (l *BPFLink) Unpin() error { retC := C.bpf_link__unpin(l.link) - C.free(unsafe.Pointer(pathC)) if retC < 0 { - return fmt.Errorf("failed to unpin link %s from path %s: %w", l.eventName, pinPath, syscall.Errno(-retC)) + return fmt.Errorf("failed to unpin link %s: %w", l.eventName, syscall.Errno(-retC)) } return nil } From 87088b68107e9d3db74117d25b4522f26649be18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 28 Aug 2023 10:24:13 -0300 Subject: [PATCH 07/11] chore: standardize defer use Applied the use of 'defer' for better resource management. --- link.go | 3 ++- module.go | 6 ++++-- prog.go | 23 +++++++++++++++-------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/link.go b/link.go index d93f75d7..f32ae385 100644 --- a/link.go +++ b/link.go @@ -85,8 +85,9 @@ func (l *BPFLink) GetFd() int { func (l *BPFLink) Pin(pinPath string) error { pathC := C.CString(pinPath) + defer C.free(unsafe.Pointer(pathC)) + retC := C.bpf_link__pin(l.link, pathC) - C.free(unsafe.Pointer(pathC)) if retC < 0 { return fmt.Errorf("failed to pin link %s to path %s: %w", l.eventName, pinPath, syscall.Errno(-retC)) } diff --git a/module.go b/module.go index bb5001a3..e5577748 100644 --- a/module.go +++ b/module.go @@ -238,8 +238,9 @@ func (m *Module) InitGlobalVariable(name string, value interface{}) error { func (m *Module) GetMap(mapName string) (*BPFMap, error) { mapNameC := C.CString(mapName) + defer C.free(unsafe.Pointer(mapNameC)) + bpfMapC, errno := C.bpf_object__find_map_by_name(m.obj, mapNameC) - C.free(unsafe.Pointer(mapNameC)) if bpfMapC == nil { return nil, fmt.Errorf("failed to find BPF map %s: %w", mapName, errno) } @@ -301,8 +302,9 @@ func (m *Module) GetMap(mapName string) (*BPFMap, error) { func (m *Module) GetProgram(progName string) (*BPFProg, error) { progNameC := C.CString(progName) + defer C.free(unsafe.Pointer(progNameC)) + progC, errno := C.bpf_object__find_program_by_name(m.obj, progNameC) - C.free(unsafe.Pointer(progNameC)) if progC == nil { return nil, fmt.Errorf("failed to find BPF program %s: %w", progName, errno) } diff --git a/prog.go b/prog.go index 356791bf..d50bc0e6 100644 --- a/prog.go +++ b/prog.go @@ -42,8 +42,9 @@ func (p *BPFProg) Pin(path string) error { } absPathC := C.CString(absPath) + defer C.free(unsafe.Pointer(absPathC)) + retC := C.bpf_program__pin(p.prog, absPathC) - C.free(unsafe.Pointer(absPathC)) if retC < 0 { return fmt.Errorf("failed to pin program %s to %s: %w", p.name, path, syscall.Errno(-retC)) } @@ -53,8 +54,9 @@ func (p *BPFProg) Pin(path string) error { func (p *BPFProg) Unpin(path string) error { pathC := C.CString(path) + defer C.free(unsafe.Pointer(pathC)) + retC := C.bpf_program__unpin(p.prog, pathC) - C.free(unsafe.Pointer(pathC)) if retC < 0 { return fmt.Errorf("failed to unpin program %s to %s: %w", p.name, path, syscall.Errno(-retC)) } @@ -126,8 +128,9 @@ func (p *BPFProg) AttachGeneric() (*BPFLink, error) { // the BPF program to. To attach to a kernel function specify attachProgFD as 0 func (p *BPFProg) SetAttachTarget(attachProgFD int, attachFuncName string) error { attachFuncNameC := C.CString(attachFuncName) + defer C.free(unsafe.Pointer(attachFuncNameC)) + retC := C.bpf_program__set_attach_target(p.prog, C.int(attachProgFD), attachFuncNameC) - C.free(unsafe.Pointer(attachFuncNameC)) if retC < 0 { return fmt.Errorf("failed to set attach target for program %s %s %w", p.name, attachFuncName, syscall.Errno(-retC)) } @@ -276,10 +279,11 @@ func (p *BPFProg) AttachXDP(deviceName string) (*BPFLink, error) { func (p *BPFProg) AttachTracepoint(category, name string) (*BPFLink, error) { tpCategoryC := C.CString(category) + defer C.free(unsafe.Pointer(tpCategoryC)) tpNameC := C.CString(name) + defer C.free(unsafe.Pointer(tpNameC)) + linkC, errno := C.bpf_program__attach_tracepoint(p.prog, tpCategoryC, tpNameC) - C.free(unsafe.Pointer(tpCategoryC)) - C.free(unsafe.Pointer(tpNameC)) if linkC == nil { return nil, fmt.Errorf("failed to attach tracepoint %s to program %s: %w", name, p.name, errno) } @@ -296,8 +300,9 @@ func (p *BPFProg) AttachTracepoint(category, name string) (*BPFLink, error) { func (p *BPFProg) AttachRawTracepoint(tpEvent string) (*BPFLink, error) { tpEventC := C.CString(tpEvent) + defer C.free(unsafe.Pointer(tpEventC)) + linkC, errno := C.bpf_program__attach_raw_tracepoint(p.prog, tpEventC) - C.free(unsafe.Pointer(tpEventC)) if linkC == nil { return nil, fmt.Errorf("failed to attach raw tracepoint %s to program %s: %w", tpEvent, p.name, errno) } @@ -354,8 +359,9 @@ func (p *BPFProg) AttachKretprobe(kp string) (*BPFLink, error) { func doAttachKprobe(prog *BPFProg, kp string, isKretprobe bool) (*BPFLink, error) { kpC := C.CString(kp) + defer C.free(unsafe.Pointer(kpC)) + linkC, errno := C.bpf_program__attach_kprobe(prog.prog, C.bool(isKretprobe), kpC) - C.free(unsafe.Pointer(kpC)) if linkC == nil { return nil, fmt.Errorf("failed to attach %s k(ret)probe to program %s: %w", kp, prog.name, errno) } @@ -467,6 +473,8 @@ func (p *BPFProg) AttachURetprobe(pid int, path string, offset uint32) (*BPFLink func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offset uint32) (*BPFLink, error) { pathC := C.CString(path) + defer C.free(unsafe.Pointer(pathC)) + linkC, errno := C.bpf_program__attach_uprobe( prog.prog, C.bool(isUretprobe), @@ -474,7 +482,6 @@ func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offse pathC, C.size_t(offset), ) - C.free(unsafe.Pointer(pathC)) if linkC == nil { return nil, fmt.Errorf("failed to attach u(ret)probe to program %s:%d with pid %d: %w ", path, offset, pid, errno) } From 77c521f9fa31c5aaf01316a038870af4e52069d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 28 Aug 2023 10:34:30 -0300 Subject: [PATCH 08/11] chore(prog): remove inner name field The API must rely on the program name returned by the libbpf API. --- module-iterator.go | 2 -- module.go | 1 - prog.go | 43 +++++++++++++++++++++---------------------- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/module-iterator.go b/module-iterator.go index 29f80527..69238882 100644 --- a/module-iterator.go +++ b/module-iterator.go @@ -67,10 +67,8 @@ func (it *BPFObjectIterator) NextProgram() *BPFProg { if progC == nil { return nil } - nameC := C.bpf_program__name(progC) prog := &BPFProg{ - name: C.GoString(nameC), prog: progC, module: it.m, } diff --git a/module.go b/module.go index e5577748..1f6698bd 100644 --- a/module.go +++ b/module.go @@ -310,7 +310,6 @@ func (m *Module) GetProgram(progName string) (*BPFProg, error) { } return &BPFProg{ - name: progName, prog: progC, module: m, }, nil diff --git a/prog.go b/prog.go index d50bc0e6..0cddfa00 100644 --- a/prog.go +++ b/prog.go @@ -20,7 +20,6 @@ import ( // type BPFProg struct { - name string prog *C.struct_bpf_program module *Module pinnedPath string @@ -46,7 +45,7 @@ func (p *BPFProg) Pin(path string) error { retC := C.bpf_program__pin(p.prog, absPathC) if retC < 0 { - return fmt.Errorf("failed to pin program %s to %s: %w", p.name, path, syscall.Errno(-retC)) + return fmt.Errorf("failed to pin program %s to %s: %w", p.Name(), path, syscall.Errno(-retC)) } p.pinnedPath = absPath return nil @@ -58,7 +57,7 @@ func (p *BPFProg) Unpin(path string) error { retC := C.bpf_program__unpin(p.prog, pathC) if retC < 0 { - return fmt.Errorf("failed to unpin program %s to %s: %w", p.name, path, syscall.Errno(-retC)) + return fmt.Errorf("failed to unpin program %s to %s: %w", p.Name(), path, syscall.Errno(-retC)) } p.pinnedPath = "" return nil @@ -119,7 +118,7 @@ func (p *BPFProg) AttachGeneric() (*BPFLink, error) { link: linkC, prog: p, linkType: Tracing, - eventName: fmt.Sprintf("tracing-%s", p.name), + eventName: fmt.Sprintf("tracing-%s", p.Name()), } return bpfLink, nil } @@ -132,7 +131,7 @@ func (p *BPFProg) SetAttachTarget(attachProgFD int, attachFuncName string) error retC := C.bpf_program__set_attach_target(p.prog, C.int(attachProgFD), attachFuncNameC) if retC < 0 { - return fmt.Errorf("failed to set attach target for program %s %s %w", p.name, attachFuncName, syscall.Errno(-retC)) + return fmt.Errorf("failed to set attach target for program %s %s %w", p.Name(), attachFuncName, syscall.Errno(-retC)) } return nil } @@ -170,7 +169,7 @@ func (p *BPFProg) AttachCgroup(cgroupV2DirPath string) (*BPFLink, error) { linkC, errno := C.bpf_program__attach_cgroup(p.prog, C.int(cgroupDirFD)) if linkC == nil { - return nil, fmt.Errorf("failed to attach cgroup on cgroupv2 %s to program %s: %w", cgroupV2DirPath, p.name, errno) + return nil, fmt.Errorf("failed to attach cgroup on cgroupv2 %s to program %s: %w", cgroupV2DirPath, p.Name(), errno) } // dirName will be used in bpfLink.eventName. eventName follows a format @@ -183,7 +182,7 @@ func (p *BPFProg) AttachCgroup(cgroupV2DirPath string) (*BPFLink, error) { link: linkC, prog: p, linkType: Cgroup, - eventName: fmt.Sprintf("cgroup-%s-%s", p.name, dirName), + eventName: fmt.Sprintf("cgroup-%s-%s", p.Name(), dirName), } p.module.links = append(p.module.links, bpfLink) return bpfLink, nil @@ -215,7 +214,7 @@ func (p *BPFProg) AttachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttac C.int(attachType), ) if retC < 0 { - return nil, fmt.Errorf("failed to attach (legacy) program %s to cgroupv2 %s: %w", p.name, cgroupV2DirPath, errno) + return nil, fmt.Errorf("failed to attach (legacy) program %s to cgroupv2 %s: %w", p.Name(), cgroupV2DirPath, errno) } dirName := strings.ReplaceAll(cgroupV2DirPath[1:], "/", "-") @@ -226,7 +225,7 @@ func (p *BPFProg) AttachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttac fakeBpfLink := &BPFLink{ link: nil, // detach/destroy made with progfd prog: p, - eventName: fmt.Sprintf("cgroup-%s-%s", p.name, dirName), + eventName: fmt.Sprintf("cgroup-%s-%s", p.Name(), dirName), // info bellow needed for detach (there isn't a real ebpf link) linkType: CgroupLegacy, legacy: bpfLinkLegacy, @@ -252,7 +251,7 @@ func (p *BPFProg) DetachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttac C.int(attachType), ) if retC < 0 { - return fmt.Errorf("failed to detach (legacy) program %s from cgroupv2 %s: %w", p.name, cgroupV2DirPath, errno) + return fmt.Errorf("failed to detach (legacy) program %s from cgroupv2 %s: %w", p.Name(), cgroupV2DirPath, errno) } return nil } @@ -264,14 +263,14 @@ func (p *BPFProg) AttachXDP(deviceName string) (*BPFLink, error) { } linkC, errno := C.bpf_program__attach_xdp(p.prog, C.int(iface.Index)) if linkC == nil { - return nil, fmt.Errorf("failed to attach xdp on device %s to program %s: %w", deviceName, p.name, errno) + return nil, fmt.Errorf("failed to attach xdp on device %s to program %s: %w", deviceName, p.Name(), errno) } bpfLink := &BPFLink{ link: linkC, prog: p, linkType: XDP, - eventName: fmt.Sprintf("xdp-%s-%s", p.name, deviceName), + eventName: fmt.Sprintf("xdp-%s-%s", p.Name(), deviceName), } p.module.links = append(p.module.links, bpfLink) return bpfLink, nil @@ -285,7 +284,7 @@ func (p *BPFProg) AttachTracepoint(category, name string) (*BPFLink, error) { linkC, errno := C.bpf_program__attach_tracepoint(p.prog, tpCategoryC, tpNameC) if linkC == nil { - return nil, fmt.Errorf("failed to attach tracepoint %s to program %s: %w", name, p.name, errno) + return nil, fmt.Errorf("failed to attach tracepoint %s to program %s: %w", name, p.Name(), errno) } bpfLink := &BPFLink{ @@ -304,7 +303,7 @@ func (p *BPFProg) AttachRawTracepoint(tpEvent string) (*BPFLink, error) { linkC, errno := C.bpf_program__attach_raw_tracepoint(p.prog, tpEventC) if linkC == nil { - return nil, fmt.Errorf("failed to attach raw tracepoint %s to program %s: %w", tpEvent, p.name, errno) + return nil, fmt.Errorf("failed to attach raw tracepoint %s to program %s: %w", tpEvent, p.Name(), errno) } bpfLink := &BPFLink{ @@ -320,7 +319,7 @@ func (p *BPFProg) AttachRawTracepoint(tpEvent string) (*BPFLink, error) { func (p *BPFProg) AttachLSM() (*BPFLink, error) { linkC, errno := C.bpf_program__attach_lsm(p.prog) if linkC == nil { - return nil, fmt.Errorf("failed to attach lsm to program %s: %w", p.name, errno) + return nil, fmt.Errorf("failed to attach lsm to program %s: %w", p.Name(), errno) } bpfLink := &BPFLink{ @@ -335,7 +334,7 @@ func (p *BPFProg) AttachLSM() (*BPFLink, error) { func (p *BPFProg) AttachPerfEvent(fd int) (*BPFLink, error) { linkC, errno := C.bpf_program__attach_perf_event(p.prog, C.int(fd)) if linkC == nil { - return nil, fmt.Errorf("failed to attach perf event to program %s: %w", p.name, errno) + return nil, fmt.Errorf("failed to attach perf event to program %s: %w", p.Name(), errno) } bpfLink := &BPFLink{ @@ -363,7 +362,7 @@ func doAttachKprobe(prog *BPFProg, kp string, isKretprobe bool) (*BPFLink, error linkC, errno := C.bpf_program__attach_kprobe(prog.prog, C.bool(isKretprobe), kpC) if linkC == nil { - return nil, fmt.Errorf("failed to attach %s k(ret)probe to program %s: %w", kp, prog.name, errno) + return nil, fmt.Errorf("failed to attach %s k(ret)probe to program %s: %w", kp, prog.Name(), errno) } kpType := Kprobe @@ -388,7 +387,7 @@ func (p *BPFProg) AttachNetns(networkNamespacePath string) (*BPFLink, error) { } linkC, errno := C.bpf_program__attach_netns(p.prog, C.int(fd)) if linkC == nil { - return nil, fmt.Errorf("failed to attach network namespace on %s to program %s: %w", networkNamespacePath, p.name, errno) + return nil, fmt.Errorf("failed to attach network namespace on %s to program %s: %w", networkNamespacePath, p.Name(), errno) } // fileName will be used in bpfLink.eventName. eventName follows a format @@ -401,7 +400,7 @@ func (p *BPFProg) AttachNetns(networkNamespacePath string) (*BPFLink, error) { link: linkC, prog: p, linkType: Netns, - eventName: fmt.Sprintf("netns-%s-%s", p.name, fileName), + eventName: fmt.Sprintf("netns-%s-%s", p.Name(), fileName), } p.module.links = append(p.module.links, bpfLink) return bpfLink, nil @@ -428,15 +427,15 @@ func (p *BPFProg) AttachIter(opts IterOpts) (*BPFLink, error) { C.uint(opts.PidFd), ) if optsC == nil { - return nil, fmt.Errorf("failed to create iter_attach_opts to program %s: %w", p.name, errno) + return nil, fmt.Errorf("failed to create iter_attach_opts to program %s: %w", p.Name(), errno) } defer C.cgo_bpf_iter_attach_opts_free(optsC) linkC, errno := C.bpf_program__attach_iter(p.prog, optsC) if linkC == nil { - return nil, fmt.Errorf("failed to attach iter to program %s: %w", p.name, errno) + return nil, fmt.Errorf("failed to attach iter to program %s: %w", p.Name(), errno) } - eventName := fmt.Sprintf("iter-%s-%d", p.name, opts.MapFd) + eventName := fmt.Sprintf("iter-%s-%d", p.Name(), opts.MapFd) bpfLink := &BPFLink{ link: linkC, prog: p, From 0228f12d6edffc02623802edb2872744fb658fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 28 Aug 2023 11:16:55 -0300 Subject: [PATCH 09/11] chore: give breath to the code putting spaces This adds spaces to the code to make it more readable. This also puts RingBuffer.Stop() and PerfBuffer.Stop() happy path out of the branching logic. --- buf-perf.go | 56 +++++++++++++++++++++++++--------------------- buf-ring.go | 51 ++++++++++++++++++++++------------------- elf.go | 1 + libbpf_cb.go | 1 + libbpfgo.go | 4 ++++ link.go | 6 +++++ module-iterator.go | 2 ++ module.go | 1 + prog.go | 40 ++++++++++++++++++++++++++++----- 9 files changed, 108 insertions(+), 54 deletions(-) diff --git a/buf-perf.go b/buf-perf.go index 2bf1edfd..463c6b04 100644 --- a/buf-perf.go +++ b/buf-perf.go @@ -41,45 +41,48 @@ func (pb *PerfBuffer) Start() { } func (pb *PerfBuffer) Stop() { - if pb.stop != nil { - // Tell the poll goroutine that it's time to exit - close(pb.stop) - - // The event and lost channels should be drained here since the consumer - // may have stopped at this point. Failure to drain it will - // result in a deadlock: the channel will fill up and the poll - // goroutine will block in the callback. - go func() { - // revive:disable:empty-block - for range pb.eventsChan { - } + if pb.stop == nil { + return + } - if pb.lostChan != nil { - for range pb.lostChan { - } - } - // revive:enable:empty-block - }() + // Signal the poll goroutine to exit + close(pb.stop) - // Wait for the poll goroutine to exit - pb.wg.Wait() + // The event and lost channels should be drained here since the consumer + // may have stopped at this point. Failure to drain it will + // result in a deadlock: the channel will fill up and the poll + // goroutine will block in the callback. + go func() { + // revive:disable:empty-block + for range pb.eventsChan { + } - // Close the channel -- this is useful for the consumer but - // also to terminate the drain goroutine above. - close(pb.eventsChan) if pb.lostChan != nil { - close(pb.lostChan) + for range pb.lostChan { + } } + // revive:enable:empty-block + }() + + // Wait for the poll goroutine to exit + pb.wg.Wait() - // This allows Stop() to be called multiple times safely - pb.stop = nil + // Close the channel -- this is useful for the consumer but + // also to terminate the drain goroutine above. + close(pb.eventsChan) + if pb.lostChan != nil { + close(pb.lostChan) } + + // Reset pb.stop to allow multiple safe calls to Stop() + pb.stop = nil } func (pb *PerfBuffer) Close() { if pb.closed { return } + pb.Stop() C.perf_buffer__free(pb.pb) eventChannels.remove(pb.slot) @@ -101,6 +104,7 @@ func (pb *PerfBuffer) poll(timeout int) error { if errno == syscall.EINTR { continue } + return fmt.Errorf("error polling perf buffer: %w", errno) } } diff --git a/buf-ring.go b/buf-ring.go index a4ee7d64..8e83b304 100644 --- a/buf-ring.go +++ b/buf-ring.go @@ -39,38 +39,41 @@ func (rb *RingBuffer) Start() { } func (rb *RingBuffer) Stop() { - if rb.stop != nil { - // Tell the poll goroutine that it's time to exit - close(rb.stop) - - // The event channel should be drained here since the consumer - // may have stopped at this point. Failure to drain it will - // result in a deadlock: the channel will fill up and the poll - // goroutine will block in the callback. - eventChan := eventChannels.get(rb.slot).(chan []byte) - go func() { - // revive:disable:empty-block - for range eventChan { - } - // revive:enable:empty-block - }() + if rb.stop == nil { + return + } - // Wait for the poll goroutine to exit - rb.wg.Wait() + // Signal the poll goroutine to exit + close(rb.stop) + + // The event channel should be drained here since the consumer + // may have stopped at this point. Failure to drain it will + // result in a deadlock: the channel will fill up and the poll + // goroutine will block in the callback. + eventChan := eventChannels.get(rb.slot).(chan []byte) + go func() { + // revive:disable:empty-block + for range eventChan { + } + // revive:enable:empty-block + }() - // Close the channel -- this is useful for the consumer but - // also to terminate the drain goroutine above. - close(eventChan) + // Wait for the poll goroutine to exit + rb.wg.Wait() - // This allows Stop() to be called multiple times safely - rb.stop = nil - } + // Close the channel -- this is useful for the consumer but + // also to terminate the drain goroutine above. + close(eventChan) + + // Reset pb.stop to allow multiple safe calls to Stop() + rb.stop = nil } func (rb *RingBuffer) Close() { if rb.closed { return } + rb.Stop() C.ring_buffer__free(rb.rb) eventChannels.remove(rb.slot) @@ -100,8 +103,10 @@ func (rb *RingBuffer) poll(timeout int) error { if errno == syscall.EINTR { continue } + return fmt.Errorf("error polling ring buffer: %w", errno) } } + return nil } diff --git a/elf.go b/elf.go index 616895cf..d328a4f3 100644 --- a/elf.go +++ b/elf.go @@ -57,5 +57,6 @@ func isGlobalVariableSection(sectionName string) bool { strings.HasPrefix(sectionName, ".rodata.") { return true } + return false } diff --git a/libbpf_cb.go b/libbpf_cb.go index 0a5a1ca4..3a58190e 100644 --- a/libbpf_cb.go +++ b/libbpf_cb.go @@ -28,6 +28,7 @@ func perfLostCallback(ctx unsafe.Pointer, cpu C.int, cnt C.ulonglong) { func ringbufferCallback(ctx unsafe.Pointer, data unsafe.Pointer, size C.int) C.int { ch := eventChannels.get(uint(uintptr(ctx))).(chan []byte) ch <- C.GoBytes(data, size) + return C.int(0) } diff --git a/libbpfgo.go b/libbpfgo.go index 62a68d77..9d017fee 100644 --- a/libbpfgo.go +++ b/libbpfgo.go @@ -65,6 +65,7 @@ func (b LibbpfStrictMode) String() (str string) { if !ok { str = LibbpfStrictModeNone.String() } + return str } @@ -82,6 +83,7 @@ func BPFProgramTypeIsSupported(progType BPFProgType) (bool, error) { if supportedC < 1 { return false, syscall.Errno(-supportedC) } + return supportedC == 1, nil } @@ -90,6 +92,7 @@ func BPFMapTypeIsSupported(mapType MapType) (bool, error) { if supportedC < 1 { return false, syscall.Errno(-supportedC) } + return supportedC == 1, nil } @@ -102,5 +105,6 @@ func NumPossibleCPUs() (int, error) { if nCPUsC < 0 { return 0, fmt.Errorf("failed to retrieve the number of CPUs: %w", syscall.Errno(-nCPUsC)) } + return int(nCPUsC), nil } diff --git a/link.go b/link.go index f32ae385..a5990872 100644 --- a/link.go +++ b/link.go @@ -60,6 +60,7 @@ func (l *BPFLink) DestroyLegacy(linkType LinkType) error { l.legacy.attachType, ) } + return fmt.Errorf("unable to destroy legacy link") } @@ -70,7 +71,9 @@ func (l *BPFLink) Destroy() error { if retC := C.bpf_link__destroy(l.link); retC < 0 { return syscall.Errno(-retC) } + l.link = nil + return nil } @@ -91,6 +94,7 @@ func (l *BPFLink) Pin(pinPath string) error { if retC < 0 { return fmt.Errorf("failed to pin link %s to path %s: %w", l.eventName, pinPath, syscall.Errno(-retC)) } + return nil } @@ -99,6 +103,7 @@ func (l *BPFLink) Unpin() error { if retC < 0 { return fmt.Errorf("failed to unpin link %s: %w", l.eventName, syscall.Errno(-retC)) } + return nil } @@ -111,6 +116,7 @@ func (l *BPFLink) Reader() (*BPFLinkReader, error) { if fdC < 0 { return nil, fmt.Errorf("failed to create reader: %w", syscall.Errno(-fdC)) } + return &BPFLinkReader{ l: l, fd: int(fdC), diff --git a/module-iterator.go b/module-iterator.go index 69238882..c062e7e2 100644 --- a/module-iterator.go +++ b/module-iterator.go @@ -72,6 +72,8 @@ func (it *BPFObjectIterator) NextProgram() *BPFProg { prog: progC, module: it.m, } + it.prevProg = prog + return prog } diff --git a/module.go b/module.go index 1f6698bd..ea93d820 100644 --- a/module.go +++ b/module.go @@ -349,6 +349,7 @@ func (m *Module) InitPerfBuf(mapName string, eventsChan chan []byte, lostChan ch if err != nil { return nil, fmt.Errorf("failed to init perf buffer: %v", err) } + if eventsChan == nil { return nil, fmt.Errorf("failed to init perf buffer: events channel can not be nil") } diff --git a/prog.go b/prog.go index 0cddfa00..97798c56 100644 --- a/prog.go +++ b/prog.go @@ -47,7 +47,9 @@ func (p *BPFProg) Pin(path string) error { if retC < 0 { return fmt.Errorf("failed to pin program %s to %s: %w", p.Name(), path, syscall.Errno(-retC)) } + p.pinnedPath = absPath + return nil } @@ -59,7 +61,9 @@ func (p *BPFProg) Unpin(path string) error { if retC < 0 { return fmt.Errorf("failed to unpin program %s to %s: %w", p.Name(), path, syscall.Errno(-retC)) } + p.pinnedPath = "" + return nil } @@ -103,6 +107,7 @@ func (p *BPFProg) SetAutoload(autoload bool) error { if retC < 0 { return fmt.Errorf("failed to set bpf program autoload: %w", syscall.Errno(-retC)) } + return nil } @@ -114,13 +119,13 @@ func (p *BPFProg) AttachGeneric() (*BPFLink, error) { if linkC == nil { return nil, fmt.Errorf("failed to attach program: %w", errno) } - bpfLink := &BPFLink{ + + return &BPFLink{ link: linkC, prog: p, linkType: Tracing, eventName: fmt.Sprintf("tracing-%s", p.Name()), - } - return bpfLink, nil + }, nil } // SetAttachTarget can be used to specify the program and/or function to attach @@ -133,6 +138,7 @@ func (p *BPFProg) SetAttachTarget(attachProgFD int, attachFuncName string) error if retC < 0 { return fmt.Errorf("failed to set attach target for program %s %s %w", p.Name(), attachFuncName, syscall.Errno(-retC)) } + return nil } @@ -152,10 +158,12 @@ func getCgroupDirFD(cgroupV2DirPath string) (int, error) { O_RDONLY int = syscall.O_RDONLY ) // revive:enable + fd, err := syscall.Open(cgroupV2DirPath, O_DIRECTORY|O_RDONLY, 0) if fd < 0 { return 0, fmt.Errorf("failed to open cgroupv2 directory path %s: %w", cgroupV2DirPath, err) } + return fd, nil } @@ -178,6 +186,7 @@ func (p *BPFProg) AttachCgroup(cgroupV2DirPath string) (*BPFLink, error) { // cgroup-progName-/sys/fs/cgroup/unified/ would look weird so replace it // to be cgroup-progName-sys-fs-cgroup-unified instead. dirName := strings.ReplaceAll(cgroupV2DirPath[1:], "/", "-") + bpfLink := &BPFLink{ link: linkC, prog: p, @@ -185,6 +194,7 @@ func (p *BPFProg) AttachCgroup(cgroupV2DirPath string) (*BPFLink, error) { eventName: fmt.Sprintf("cgroup-%s-%s", p.Name(), dirName), } p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil } @@ -202,12 +212,14 @@ func (p *BPFProg) AttachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttac if err == nil { return bpfLink, nil } + // Try the legacy attachment method before fully failing cgroupDirFD, err := getCgroupDirFD(cgroupV2DirPath) if err != nil { return nil, err } defer syscall.Close(cgroupDirFD) + retC, errno := C.cgo_bpf_prog_attach_cgroup_legacy( C.int(p.FileDescriptor()), C.int(cgroupDirFD), @@ -216,6 +228,7 @@ func (p *BPFProg) AttachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttac if retC < 0 { return nil, fmt.Errorf("failed to attach (legacy) program %s to cgroupv2 %s: %w", p.Name(), cgroupV2DirPath, errno) } + dirName := strings.ReplaceAll(cgroupV2DirPath[1:], "/", "-") bpfLinkLegacy := &bpfLinkLegacy{ @@ -230,6 +243,7 @@ func (p *BPFProg) AttachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttac linkType: CgroupLegacy, legacy: bpfLinkLegacy, } + return fakeBpfLink, nil } @@ -245,6 +259,7 @@ func (p *BPFProg) DetachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttac return err } defer syscall.Close(cgroupDirFD) + retC, errno := C.cgo_bpf_prog_detach_cgroup_legacy( C.int(p.FileDescriptor()), C.int(cgroupDirFD), @@ -253,6 +268,7 @@ func (p *BPFProg) DetachCgroupLegacy(cgroupV2DirPath string, attachType BPFAttac if retC < 0 { return fmt.Errorf("failed to detach (legacy) program %s from cgroupv2 %s: %w", p.Name(), cgroupV2DirPath, errno) } + return nil } @@ -261,6 +277,7 @@ func (p *BPFProg) AttachXDP(deviceName string) (*BPFLink, error) { if err != nil { return nil, fmt.Errorf("failed to find device by name %s: %w", deviceName, err) } + linkC, errno := C.bpf_program__attach_xdp(p.prog, C.int(iface.Index)) if linkC == nil { return nil, fmt.Errorf("failed to attach xdp on device %s to program %s: %w", deviceName, p.Name(), errno) @@ -273,6 +290,7 @@ func (p *BPFProg) AttachXDP(deviceName string) (*BPFLink, error) { eventName: fmt.Sprintf("xdp-%s-%s", p.Name(), deviceName), } p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil } @@ -294,6 +312,7 @@ func (p *BPFProg) AttachTracepoint(category, name string) (*BPFLink, error) { eventName: name, } p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil } @@ -313,6 +332,7 @@ func (p *BPFProg) AttachRawTracepoint(tpEvent string) (*BPFLink, error) { eventName: tpEvent, } p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil } @@ -328,6 +348,7 @@ func (p *BPFProg) AttachLSM() (*BPFLink, error) { linkType: LSM, } p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil } @@ -343,6 +364,7 @@ func (p *BPFProg) AttachPerfEvent(fd int) (*BPFLink, error) { linkType: PerfEvent, } p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil } @@ -377,6 +399,7 @@ func doAttachKprobe(prog *BPFProg, kp string, isKretprobe bool) (*BPFLink, error eventName: kp, } prog.module.links = append(prog.module.links, bpfLink) + return bpfLink, nil } @@ -385,6 +408,7 @@ func (p *BPFProg) AttachNetns(networkNamespacePath string) (*BPFLink, error) { if fd < 0 { return nil, fmt.Errorf("failed to open network namespace path %s: %w", networkNamespacePath, err) } + linkC, errno := C.bpf_program__attach_netns(p.prog, C.int(fd)) if linkC == nil { return nil, fmt.Errorf("failed to attach network namespace on %s to program %s: %w", networkNamespacePath, p.Name(), errno) @@ -396,6 +420,7 @@ func (p *BPFProg) AttachNetns(networkNamespacePath string) (*BPFLink, error) { // netns-progName-/proc/self/ns/net would look weird so replace it // to be netns-progName-proc-self-ns-net instead. fileName := strings.ReplaceAll(networkNamespacePath[1:], "/", "-") + bpfLink := &BPFLink{ link: linkC, prog: p, @@ -403,6 +428,7 @@ func (p *BPFProg) AttachNetns(networkNamespacePath string) (*BPFLink, error) { eventName: fmt.Sprintf("netns-%s-%s", p.Name(), fileName), } p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil } @@ -435,14 +461,15 @@ func (p *BPFProg) AttachIter(opts IterOpts) (*BPFLink, error) { if linkC == nil { return nil, fmt.Errorf("failed to attach iter to program %s: %w", p.Name(), errno) } - eventName := fmt.Sprintf("iter-%s-%d", p.Name(), opts.MapFd) + bpfLink := &BPFLink{ link: linkC, prog: p, linkType: Iter, - eventName: eventName, + eventName: fmt.Sprintf("iter-%s-%d", p.Name(), opts.MapFd), } p.module.links = append(p.module.links, bpfLink) + return bpfLink, nil } @@ -496,6 +523,7 @@ func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offse linkType: upType, eventName: fmt.Sprintf("%s:%d:%d", path, pid, offset), } + return bpfLink, nil } @@ -510,6 +538,7 @@ func (p *BPFProg) AttachGenericFD(targetFd int, attachType BPFAttachType, flags if retC < 0 { return fmt.Errorf("failed to attach: %w", syscall.Errno(-retC)) } + return nil } @@ -523,5 +552,6 @@ func (p *BPFProg) DetachGenericFD(targetFd int, attachType BPFAttachType) error if retC < 0 { return fmt.Errorf("failed to detach: %w", syscall.Errno(-retC)) } + return nil } From 62f0ccc2b85b17a3b3d0d45fc8a09012cf02161c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 28 Aug 2023 12:31:42 -0300 Subject: [PATCH 10/11] chore(prog): add TODO --- prog.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prog.go b/prog.go index 97798c56..9d78eb06 100644 --- a/prog.go +++ b/prog.go @@ -142,10 +142,12 @@ func (p *BPFProg) SetAttachTarget(attachProgFD int, attachFuncName string) error return nil } +// TODO: fix API to return error func (p *BPFProg) SetProgramType(progType BPFProgType) { C.bpf_program__set_type(p.prog, C.enum_bpf_prog_type(int(progType))) } +// TODO: fix API to return error func (p *BPFProg) SetAttachType(attachType BPFAttachType) { C.bpf_program__set_expected_attach_type(p.prog, C.enum_bpf_attach_type(int(attachType))) } From f1738f934321bb3cb68ccbc47d39627085a0d8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 28 Aug 2023 17:11:13 -0300 Subject: [PATCH 11/11] chore: consider errno in NULL return --- module-iterator.go | 6 ++++-- module.go | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/module-iterator.go b/module-iterator.go index c062e7e2..3bdf0aec 100644 --- a/module-iterator.go +++ b/module-iterator.go @@ -23,8 +23,9 @@ func (it *BPFObjectIterator) NextMap() *BPFMap { startMapC = it.prevMap.bpfMap } - bpfMapC := C.bpf_object__next_map(it.m.obj, startMapC) + bpfMapC, errno := C.bpf_object__next_map(it.m.obj, startMapC) if bpfMapC == nil { + _ = errno // intentionally ignored return nil } @@ -63,8 +64,9 @@ func (it *BPFObjectIterator) NextProgram() *BPFProg { startProg = it.prevProg.prog } - progC := C.bpf_object__next_program(it.m.obj, startProg) + progC, errno := C.bpf_object__next_program(it.m.obj, startProg) if progC == nil { + _ = errno // intentionally ignored return nil } diff --git a/module.go b/module.go index ea93d820..139503dc 100644 --- a/module.go +++ b/module.go @@ -330,9 +330,9 @@ func (m *Module) InitRingBuf(mapName string, eventsChan chan []byte) (*RingBuffe return nil, fmt.Errorf("max ring buffers reached") } - rbC := C.cgo_init_ring_buf(C.int(bpfMap.FileDescriptor()), C.uintptr_t(slot)) + rbC, errno := C.cgo_init_ring_buf(C.int(bpfMap.FileDescriptor()), C.uintptr_t(slot)) if rbC == nil { - return nil, fmt.Errorf("failed to initialize ring buffer") + return nil, fmt.Errorf("failed to initialize ring buffer: %w", errno) } ringBuf := &RingBuffer{ @@ -365,10 +365,10 @@ func (m *Module) InitPerfBuf(mapName string, eventsChan chan []byte, lostChan ch return nil, fmt.Errorf("max number of ring/perf buffers reached") } - pbC := C.cgo_init_perf_buf(C.int(bpfMap.FileDescriptor()), C.int(pageCnt), C.uintptr_t(slot)) + pbC, errno := C.cgo_init_perf_buf(C.int(bpfMap.FileDescriptor()), C.int(pageCnt), C.uintptr_t(slot)) if pbC == nil { eventChannels.remove(uint(slot)) - return nil, fmt.Errorf("failed to initialize perf buffer") + return nil, fmt.Errorf("failed to initialize perf buffer: %w", errno) } perfBuf.pb = pbC