-
Notifications
You must be signed in to change notification settings - Fork 95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle errno better and enoent better for batch operations #112
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -339,7 +339,7 @@ func NewModuleFromFileArgs(args NewModuleArgs) (*Module, error) { | |
} | ||
|
||
obj, errno := C.bpf_object__open_file(bpfFile, &opts) | ||
if errno != nil { | ||
if obj == nil && errno != nil { | ||
return nil, fmt.Errorf("failed to open BPF object %s: %w", args.BPFObjPath, errno) | ||
} | ||
|
||
|
@@ -381,7 +381,7 @@ func NewModuleFromBufferArgs(args NewModuleArgs) (*Module, error) { | |
} | ||
|
||
obj, errno := C.bpf_object__open_mem(bpfBuff, bpfBuffSize, &opts) | ||
if errno != nil { | ||
if obj == nil && errno != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same |
||
return nil, fmt.Errorf("failed to open BPF object %s: %w: errno", args.BPFObjName, args.BPFObjBuff[:20], errno) | ||
} | ||
|
||
|
@@ -418,8 +418,8 @@ func (m *Module) Close() { | |
} | ||
|
||
func (m *Module) BPFLoadObject() error { | ||
_, errno := C.bpf_object__load(m.obj) | ||
if errno != nil { | ||
ret, errno := C.bpf_object__load(m.obj) | ||
if ret < 0 && errno != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good test, as bpf_object__load returns an int, and uses libbpf_err to set errno in case of an error:
Same for all places that don't return a pointer |
||
return fmt.Errorf("failed to load BPF object: %w", errno) | ||
} | ||
|
||
|
@@ -430,7 +430,7 @@ func (m *Module) GetMap(mapName string) (*BPFMap, error) { | |
cs := C.CString(mapName) | ||
bpfMap, errno := C.bpf_object__find_map_by_name(m.obj, cs) | ||
C.free(unsafe.Pointer(cs)) | ||
if errno != nil { | ||
if bpfMap == nil && errno != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here it is ok to check for null bpfMap: This inconsistency in libbpf is really confusing. I think it should be fixed before 1.0 |
||
return nil, fmt.Errorf("failed to find BPF map %s: %w", mapName, errno) | ||
} | ||
|
||
|
@@ -444,29 +444,29 @@ func (m *Module) GetMap(mapName string) (*BPFMap, error) { | |
|
||
func (b *BPFMap) Pin(pinPath string) error { | ||
path := C.CString(pinPath) | ||
_, errno := C.bpf_map__pin(b.bpfMap, path) | ||
ret, errno := C.bpf_map__pin(b.bpfMap, path) | ||
C.free(unsafe.Pointer(path)) | ||
if errno != nil { | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("failed to pin map %s to path %s: %w", b.name, pinPath, errno) | ||
} | ||
return nil | ||
} | ||
|
||
func (b *BPFMap) Unpin(pinPath string) error { | ||
path := C.CString(pinPath) | ||
_, errno := C.bpf_map__unpin(b.bpfMap, path) | ||
ret, errno := C.bpf_map__unpin(b.bpfMap, path) | ||
C.free(unsafe.Pointer(path)) | ||
if errno != nil { | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("failed to unpin map %s from path %s: %w", b.name, pinPath, errno) | ||
} | ||
return nil | ||
} | ||
|
||
func (b *BPFMap) SetPinPath(pinPath string) error { | ||
path := C.CString(pinPath) | ||
_, errno := C.bpf_map__set_pin_path(b.bpfMap, path) | ||
ret, errno := C.bpf_map__set_pin_path(b.bpfMap, path) | ||
C.free(unsafe.Pointer(path)) | ||
if errno != nil { | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("failed to set pin for map %s to path %s: %w", b.name, pinPath, errno) | ||
} | ||
return nil | ||
|
@@ -478,8 +478,8 @@ func (b *BPFMap) SetPinPath(pinPath string) error { | |
// Note: for ring buffer and perf buffer, maxEntries is the | ||
// capacity in bytes. | ||
func (b *BPFMap) Resize(maxEntries uint32) error { | ||
_, errno := C.bpf_map__set_max_entries(b.bpfMap, C.uint(maxEntries)) | ||
if errno != nil { | ||
ret, errno := C.bpf_map__set_max_entries(b.bpfMap, C.uint(maxEntries)) | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("failed to resize map %s to %v: %w", b.name, maxEntries, errno) | ||
} | ||
return nil | ||
|
@@ -562,8 +562,8 @@ func (b *BPFMap) GetValue(key unsafe.Pointer) ([]byte, error) { | |
value := make([]byte, b.ValueSize()) | ||
valuePtr := unsafe.Pointer(&value[0]) | ||
|
||
_, errno := C.bpf_map_lookup_elem(b.fd, key, valuePtr) | ||
if errno != nil { | ||
ret, errno := C.bpf_map_lookup_elem(b.fd, key, valuePtr) | ||
if ret < 0 && errno != nil { | ||
return nil, fmt.Errorf("failed to lookup value %v in map %s: %w", key, b.name, errno) | ||
} | ||
return value, nil | ||
|
@@ -608,14 +608,19 @@ func (b *BPFMap) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.Poi | |
Flags: C.BPF_ANY, | ||
} | ||
|
||
_, errno := C.bpf_map_lookup_batch(b.fd, startKey, nextKey, keys, valuesPtr, &countC, bpfMapBatchOptsToC(opts)) | ||
if errno != nil { | ||
return nil, fmt.Errorf("failed to batch lookup values %v in map %s: %w", keys, b.name, errno) | ||
var reterr error | ||
ret, errno := C.bpf_map_lookup_batch(b.fd, startKey, nextKey, keys, valuesPtr, &countC, bpfMapBatchOptsToC(opts)) | ||
if ret < 0 && errno != nil { | ||
if errors.Is(errno, syscall.ENOENT) { | ||
reterr = errno | ||
} else { | ||
return nil, fmt.Errorf("failed to batch lookup values %v in map %s: %w", keys, b.name, errno) | ||
} | ||
} | ||
|
||
parsedVals := collectBatchValues(values, count, b.ValueSize()) | ||
|
||
return parsedVals, nil | ||
return parsedVals, reterr | ||
} | ||
|
||
// GetValueAndDeleteBatch allows for batch lookups of multiple keys and deletes those keys. | ||
|
@@ -632,14 +637,19 @@ func (b *BPFMap) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointer, | |
Flags: C.BPF_ANY, | ||
} | ||
|
||
_, errno := C.bpf_map_lookup_and_delete_batch(b.fd, startKey, nextKey, keys, valuesPtr, &countC, bpfMapBatchOptsToC(opts)) | ||
if errno != nil { | ||
return nil, fmt.Errorf("failed to batch lookup and delete values %v in map %s: %w", keys, b.name, errno) | ||
var reterr error | ||
ret, errno := C.bpf_map_lookup_and_delete_batch(b.fd, startKey, nextKey, keys, valuesPtr, &countC, bpfMapBatchOptsToC(opts)) | ||
if ret < 0 && errno != nil { | ||
if errors.Is(errno, syscall.ENOENT) { | ||
reterr = errno | ||
} else { | ||
return nil, fmt.Errorf("failed to batch lookup and delete values %v in map %s: %w", keys, b.name, errno) | ||
} | ||
} | ||
|
||
parsedVals := collectBatchValues(values, count, b.ValueSize()) | ||
|
||
return parsedVals, nil | ||
return parsedVals, reterr | ||
} | ||
|
||
func collectBatchValues(values []byte, count uint32, valueSize int) [][]byte { | ||
|
@@ -660,8 +670,8 @@ func (b *BPFMap) UpdateBatch(keys, values unsafe.Pointer, count uint32) error { | |
ElemFlags: C.BPF_ANY, | ||
Flags: C.BPF_ANY, | ||
} | ||
_, errno := C.bpf_map_update_batch(b.fd, keys, values, &countC, bpfMapBatchOptsToC(&opts)) | ||
if errno != nil { | ||
ret, errno := C.bpf_map_update_batch(b.fd, keys, values, &countC, bpfMapBatchOptsToC(&opts)) | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("failed to update map %s: %w", b.name, errno) | ||
} | ||
return nil | ||
|
@@ -676,8 +686,8 @@ func (b *BPFMap) DeleteKeyBatch(keys unsafe.Pointer, count uint32) error { | |
ElemFlags: C.BPF_ANY, | ||
Flags: C.BPF_ANY, | ||
} | ||
_, errno := C.bpf_map_delete_batch(b.fd, keys, &countC, bpfMapBatchOptsToC(opts)) | ||
if errno != nil { | ||
ret, errno := C.bpf_map_delete_batch(b.fd, keys, &countC, bpfMapBatchOptsToC(opts)) | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("failed to get lookup key %d from map %s: %w", keys, b.name, errno) | ||
} | ||
return nil | ||
|
@@ -692,8 +702,8 @@ func (b *BPFMap) DeleteKeyBatch(keys unsafe.Pointer, count uint32) error { | |
// in the slice or array instead of the slice/array itself, as to | ||
// avoid undefined behavior. | ||
func (b *BPFMap) DeleteKey(key unsafe.Pointer) error { | ||
_, errno := C.bpf_map_delete_elem(b.fd, key) | ||
if errno != nil { | ||
ret, errno := C.bpf_map_delete_elem(b.fd, key) | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("failed to get lookup key %d from map %s: %w", key, b.name, errno) | ||
} | ||
return nil | ||
|
@@ -715,8 +725,8 @@ func (b *BPFMap) DeleteKey(key unsafe.Pointer) error { | |
// bpfmap.Update(keyPtr, valuePtr) | ||
// | ||
func (b *BPFMap) Update(key, value unsafe.Pointer) error { | ||
_, errno := C.bpf_map_update_elem(b.fd, key, value, C.BPF_ANY) | ||
if errno != nil { | ||
ret, errno := C.bpf_map_update_elem(b.fd, key, value, C.BPF_ANY) | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("failed to update map %s: %w", b.name, errno) | ||
} | ||
return nil | ||
|
@@ -750,8 +760,8 @@ func (it *BPFMapIterator) Next() bool { | |
next := make([]byte, it.b.KeySize()) | ||
nextPtr := unsafe.Pointer(&next[0]) | ||
|
||
_, errno := C.bpf_map_get_next_key(it.b.fd, prevPtr, nextPtr) | ||
if errno != nil { | ||
ret, errno := C.bpf_map_get_next_key(it.b.fd, prevPtr, nextPtr) | ||
if ret < 0 && errno != nil { | ||
if errors.Is(errno, syscall.ENOENT) { | ||
it.err = errno | ||
} | ||
|
@@ -778,7 +788,7 @@ 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 errno != nil { | ||
if prog == nil && errno != nil { | ||
return nil, fmt.Errorf("failed to find BPF program %s: %w", progName, errno) | ||
} | ||
|
||
|
@@ -801,9 +811,9 @@ func (p *BPFProg) Pin(path string) error { | |
} | ||
|
||
cs := C.CString(absPath) | ||
_, errno := C.bpf_program__pin(p.prog, cs) | ||
ret, errno := C.bpf_program__pin(p.prog, cs) | ||
C.free(unsafe.Pointer(cs)) | ||
if errno != nil { | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("failed to pin program %s to %s: %w", p.name, path, errno) | ||
} | ||
p.pinnedPath = absPath | ||
|
@@ -812,9 +822,9 @@ func (p *BPFProg) Pin(path string) error { | |
|
||
func (p *BPFProg) Unpin(path string) error { | ||
cs := C.CString(path) | ||
_, errno := C.bpf_program__unpin(p.prog, cs) | ||
ret, errno := C.bpf_program__unpin(p.prog, cs) | ||
C.free(unsafe.Pointer(cs)) | ||
if errno != nil { | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("failed to unpin program %s to %s: %w", p.name, path, errno) | ||
} | ||
p.pinnedPath = "" | ||
|
@@ -876,16 +886,16 @@ func (p *BPFProg) GetType() uint32 { | |
|
||
func (p *BPFProg) SetAutoload(autoload bool) error { | ||
cbool := C.bool(autoload) | ||
_, errno := C.bpf_program__set_autoload(p.prog, cbool) | ||
if errno != nil { | ||
ret, errno := C.bpf_program__set_autoload(p.prog, cbool) | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("failed to set bpf program autoload: %w", errno) | ||
} | ||
return nil | ||
} | ||
|
||
func (p *BPFProg) SetTracepoint() error { | ||
_, errno := C.bpf_program__set_tracepoint(p.prog) | ||
if errno != nil { | ||
ret, errno := C.bpf_program__set_tracepoint(p.prog) | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("failed to set bpf program as tracepoint: %w", errno) | ||
} | ||
return nil | ||
|
@@ -897,7 +907,7 @@ func (p *BPFProg) AttachTracepoint(category, name string) (*BPFLink, error) { | |
link, errno := C.bpf_program__attach_tracepoint(p.prog, tpCategory, tpName) | ||
C.free(unsafe.Pointer(tpCategory)) | ||
C.free(unsafe.Pointer(tpName)) | ||
if errno != nil { | ||
if link == nil && errno != nil { | ||
return nil, fmt.Errorf("failed to attach tracepoint %s to program %s: %w", name, p.name, errno) | ||
} | ||
|
||
|
@@ -915,7 +925,7 @@ 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 errno != nil { | ||
if link == nil && errno != nil { | ||
return nil, fmt.Errorf("failed to attach raw tracepoint %s to program %s: %w", tpEvent, p.name, errno) | ||
} | ||
|
||
|
@@ -931,7 +941,7 @@ func (p *BPFProg) AttachRawTracepoint(tpEvent string) (*BPFLink, error) { | |
|
||
func (p *BPFProg) AttachPerfEvent(fd int) (*BPFLink, error) { | ||
link, errno := C.bpf_program__attach_perf_event(p.prog, C.int(fd)) | ||
if errno != nil { | ||
if link == nil && errno != nil { | ||
return nil, fmt.Errorf("failed to attach perf event to program %s: %w", p.name, errno) | ||
} | ||
|
||
|
@@ -956,7 +966,7 @@ func (p *BPFProg) AttachKretprobe(kp string) (*BPFLink, error) { | |
|
||
func (p *BPFProg) AttachLSM() (*BPFLink, error) { | ||
link, errno := C.bpf_program__attach_lsm(p.prog) | ||
if errno != nil { | ||
if link == nil && errno != nil { | ||
return nil, fmt.Errorf("failed to attach lsm to program %s: %w", p.name, errno) | ||
} | ||
|
||
|
@@ -974,7 +984,7 @@ func doAttachKprobe(prog *BPFProg, kp string, isKretprobe bool) (*BPFLink, error | |
cbool := C.bool(isKretprobe) | ||
link, errno := C.bpf_program__attach_kprobe(prog.prog, cbool, cs) | ||
C.free(unsafe.Pointer(cs)) | ||
if errno != nil { | ||
if link == nil && errno != nil { | ||
return nil, fmt.Errorf("failed to attach %s k(ret)probe to program %s: %w", kp, prog.name, errno) | ||
} | ||
|
||
|
@@ -1026,7 +1036,7 @@ func doAttachUprobe(prog *BPFProg, isUretprobe bool, pid int, path string, offse | |
|
||
link, errno := C.bpf_program__attach_uprobe(prog.prog, retCBool, pidCint, pathCString, offsetCsizet) | ||
C.free(unsafe.Pointer(pathCString)) | ||
if errno != nil { | ||
if link == nil && errno != nil { | ||
return nil, fmt.Errorf("failed to attach u(ret)probe to program %s:%d with pid %d: %w", path, offset, pid, errno) | ||
} | ||
|
||
|
@@ -1057,7 +1067,7 @@ func doAttachKprobeLegacy(prog *BPFProg, kp string, isKretprobe bool) (*BPFLink, | |
cbool := C.bool(isKretprobe) | ||
link, errno := C.attach_kprobe_legacy(prog.prog, cs, cbool) | ||
C.free(unsafe.Pointer(cs)) | ||
if errno != nil { | ||
if link == nil && errno != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is implemented in libbpfgo - it is ok to check for nil |
||
return nil, fmt.Errorf("failed to attach %s k(ret)probe using legacy debugfs API: %w", kp, errno) | ||
} | ||
|
||
|
@@ -1094,7 +1104,7 @@ func (m *Module) InitRingBuf(mapName string, eventsChan chan []byte) (*RingBuffe | |
} | ||
|
||
rb, errno := C.init_ring_buf(bpfMap.fd, C.uintptr_t(slot)) | ||
if errno != nil { | ||
if rb == nil && errno != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is implemented in libbpfgo - it is ok to check for nil |
||
return nil, fmt.Errorf("failed to initialize ring buffer: %w", errno) | ||
} | ||
|
||
|
@@ -1370,17 +1380,17 @@ func (hook *TcHook) SetParent(a int, b int) { | |
} | ||
|
||
func (hook *TcHook) Create() error { | ||
_, errno := C.bpf_tc_hook_create(hook.hook) | ||
if errno != nil { | ||
ret, errno := C.bpf_tc_hook_create(hook.hook) | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("could not create tc hook: %w", errno) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (hook *TcHook) Destroy() error { | ||
_, errno := C.bpf_tc_hook_destroy(hook.hook) | ||
if errno != nil { | ||
ret, errno := C.bpf_tc_hook_destroy(hook.hook) | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("could not destroy tc hook: %w", errno) | ||
} | ||
|
||
|
@@ -1389,8 +1399,8 @@ func (hook *TcHook) Destroy() error { | |
|
||
func (hook *TcHook) Attach(tcOpts *TcOpts) error { | ||
opts := tcOptsToC(tcOpts) | ||
_, errno := C.bpf_tc_attach(hook.hook, opts) | ||
if errno != nil { | ||
ret, errno := C.bpf_tc_attach(hook.hook, opts) | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("could not attach tc hook: %w", errno) | ||
} | ||
tcOptsFromC(tcOpts, opts) | ||
|
@@ -1400,8 +1410,8 @@ func (hook *TcHook) Attach(tcOpts *TcOpts) error { | |
|
||
func (hook *TcHook) Detach(tcOpts *TcOpts) error { | ||
opts := tcOptsToC(tcOpts) | ||
_, errno := C.bpf_tc_detach(hook.hook, opts) | ||
if errno != nil { | ||
ret, errno := C.bpf_tc_detach(hook.hook, opts) | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("could not detach tc hook: %w", errno) | ||
} | ||
tcOptsFromC(tcOpts, opts) | ||
|
@@ -1411,8 +1421,8 @@ func (hook *TcHook) Detach(tcOpts *TcOpts) error { | |
|
||
func (hook *TcHook) Query(tcOpts *TcOpts) error { | ||
opts := tcOptsToC(tcOpts) | ||
_, errno := C.bpf_tc_query(hook.hook, opts) | ||
if errno != nil { | ||
ret, errno := C.bpf_tc_query(hook.hook, opts) | ||
if ret < 0 && errno != nil { | ||
return fmt.Errorf("could not query tc hook: %w", errno) | ||
} | ||
tcOptsFromC(tcOpts, opts) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is checking errno not already enough to make sure there's an error? It seems that Go does clear errno before each C function call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related to the comment of @grantseltzer
libbpf might return a non-null value in some cases.
For example: https://github.com/libbpf/libbpf/blob/master/src/libbpf.c#L7080.
In that case, we will mistakenly return that no error occured
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if Go does clear it before the call it's still possible that something within the call sets
errno
but we still don't actually have an error condition, the call that we originally made did succeed.Without this change a few of the selftests error out. Also in this patch is a fix for map batch operations where
errno
could equalENOENT
. This happens when we've read an entire map, however we may still have gotten some data back so this patch fixes things to ensure whatever data we got is still returned, even if it's less than the batch amount.