From 88f1455dd7d3eddf7c23a25ec8624f926698079d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Sun, 19 Jan 2020 02:05:41 +0100 Subject: [PATCH] Pure go variant of the ebpf API Instead of using cgo we call the syscall for BPF directly from go. The API hasn't changed, however, and we also closely follow the C-implementation as given in bpf(2). Not sure if this pure go variant is beneficial. Manual maintenance of all constants and structs upon changes of the BPF API would be necessary and cumbersome. We would at least need to complement this with auto-generation of constants and fields from /usr/include/linux/bpf.h. --- GetRuntimeAddresses/ebpf/ebpf.go | 338 ++++++++++++++++++++------- GetRuntimeAddresses/ebpf/ebpf_cgo.go | 151 ++++++++++++ 2 files changed, 399 insertions(+), 90 deletions(-) create mode 100644 GetRuntimeAddresses/ebpf/ebpf_cgo.go diff --git a/GetRuntimeAddresses/ebpf/ebpf.go b/GetRuntimeAddresses/ebpf/ebpf.go index 056d295..0f77de7 100644 --- a/GetRuntimeAddresses/ebpf/ebpf.go +++ b/GetRuntimeAddresses/ebpf/ebpf.go @@ -1,149 +1,307 @@ +// +build !withcgo + package ebpf -/* -#include -#include -#include -#include -#include - -// See the bpf man page for details on the following functions - -static int bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size) -{ - return syscall(__NR_bpf, cmd, attr, size); -} - -static int bpf_create_map( - enum bpf_map_type map_type, unsigned int key_size, unsigned int value_size, - unsigned int max_entries) -{ - union bpf_attr attr = { - .map_type = map_type, - .key_size = key_size, - .value_size = value_size, - .max_entries = max_entries}; - - return bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); -} - -int bpf_get_next_key(int fd, const void *key, void *next_key) -{ - union bpf_attr attr = { - .map_fd = fd, - .key = (__u64) (unsigned long) key, - .next_key = (__u64) (unsigned long) next_key}; - - return bpf(BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr)); -} - - -int bpf_lookup_elem(int fd, const void *key, void *value) -{ - union bpf_attr attr = { - .map_fd = fd, - .key = (__u64) (unsigned long) key, - .value = (__u64) (unsigned long) value, - }; - - return bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); -} - -int bpf_update_elem(int fd, const void *key, const void *value, uint64_t flags) -{ - union bpf_attr attr = { - .map_fd = fd, - .key = (__u64) (unsigned long) key, - .value = (__u64) (unsigned long) value, - .flags = flags, - }; - - return bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); -} - -*/ -import "C" import ( "fmt" "syscall" "unsafe" ) +// All constants are taken from /usr/include/linux/bpf.h + +const BPF_SYSCALL = 321 + +type bpf_cmd int + +const ( + BPF_MAP_CREATE bpf_cmd = iota + BPF_MAP_LOOKUP_ELEM + BPF_MAP_UPDATE_ELEM + BPF_MAP_DELETE_ELEM + BPF_MAP_GET_NEXT_KEY + BPF_PROG_LOAD + BPF_OBJ_PIN + BPF_OBJ_GET + BPF_PROG_ATTACH + BPF_PROG_DETACH + BPF_PROG_TEST_RUN + BPF_PROG_GET_NEXT_ID + BPF_MAP_GET_NEXT_ID + BPF_PROG_GET_FD_BY_ID + BPF_MAP_GET_FD_BY_ID + BPF_OBJ_GET_INFO_BY_FD + BPF_PROG_QUERY + BPF_RAW_TRACEPOINT_OPEN + BPF_BTF_LOAD + BPF_BTF_GET_FD_BY_ID + BPF_TASK_FD_QUERY + BPF_MAP_LOOKUP_AND_DELETE_ELEM + BPF_MAP_FREEZE +) + +type bpf_map_type int + +const ( + BPF_MAP_TYPE_UNSPEC bpf_map_type = iota + BPF_MAP_TYPE_HASH + BPF_MAP_TYPE_ARRAY + BPF_MAP_TYPE_PROG_ARRAY + BPF_MAP_TYPE_PERF_EVENT_ARRAY + BPF_MAP_TYPE_PERCPU_HASH + BPF_MAP_TYPE_PERCPU_ARRAY + BPF_MAP_TYPE_STACK_TRACE + BPF_MAP_TYPE_CGROUP_ARRAY + BPF_MAP_TYPE_LRU_HASH + BPF_MAP_TYPE_LRU_PERCPU_HASH + BPF_MAP_TYPE_LPM_TRIE + BPF_MAP_TYPE_ARRAY_OF_MAPS + BPF_MAP_TYPE_HASH_OF_MAPS + BPF_MAP_TYPE_DEVMAP + BPF_MAP_TYPE_SOCKMAP + BPF_MAP_TYPE_CPUMAP + BPF_MAP_TYPE_XSKMAP + BPF_MAP_TYPE_SOCKHASH + BPF_MAP_TYPE_CGROUP_STORAGE + BPF_MAP_TYPE_REUSEPORT_SOCKARRAY + BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE + BPF_MAP_TYPE_QUEUE + BPF_MAP_TYPE_STACK + BPF_MAP_TYPE_SK_STORAGE +) + // MapFD is a file descriptor representing a eBPF map -type MapFD int +type MapFD uint32 + +/* + +All methods in this file implement a syscall to bpf one way or another. We +follow the C-API given in bpf(2) and have the corresponding C-functions +embedded as comments. Those all refer to the C-function bpf(), which is a +wrapper for the syscall: + + static int bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size) + { + return syscall(__NR_bpf, cmd, attr, size); + } + +Each ouf our methods calls the syscall directly, instead. + +*/ // CreateMap creates an eBPF map from int->uint64. The file descriptor of the // created map is returned. func CreateMap() (MapFD, error) { + /* + static int bpf_create_map( + enum bpf_map_type map_type, unsigned int key_size, unsigned int value_size, + unsigned int max_entries) + { + union bpf_attr attr = { + .map_type = map_type, + .key_size = key_size, + .value_size = value_size, + .max_entries = max_entries}; - r, errno := C.bpf_create_map(C.BPF_MAP_TYPE_HASH, 4, 8, 64) + return bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); + } + */ - if r == -1 { - return MapFD(r), fmt.Errorf("bpf_create_map with errno %d", errno) + create_attr := struct { + map_type uint32 + key_size uint32 + value_size uint32 + max_entries uint32 + map_flags uint32 + inner_map_fd uint32 + // minimum of required fields + }{ + map_type: uint32(BPF_MAP_TYPE_HASH), + key_size: 4, + value_size: 8, + max_entries: 64, } + r, _, err := syscall.Syscall( + BPF_SYSCALL, + uintptr(BPF_MAP_CREATE), + uintptr(unsafe.Pointer(&create_attr)), + unsafe.Sizeof(create_attr), + ) + + if err != 0 { + return 0, err + } return MapFD(r), nil } +func (mfd MapFD) bpf_get_next_key(key *int, next_key *int) error { + /* + int bpf_get_next_key(int fd, const void *key, void *next_key) + { + union bpf_attr attr = { + .map_fd = fd, + .key = (__u64) (unsigned long) key, + .next_key = (__u64) (unsigned long) next_key}; + + return bpf(BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr)); + } + */ + + next_attr := struct { + map_fd MapFD + key uint64 + next_key uint64 + }{ + map_fd: mfd, + key: uint64(uintptr(unsafe.Pointer(key))), + next_key: uint64(uintptr(unsafe.Pointer(next_key))), + } + + r, _, err := syscall.Syscall( + BPF_SYSCALL, + uintptr(BPF_MAP_GET_NEXT_KEY), + uintptr(unsafe.Pointer(&next_attr)), + unsafe.Sizeof(next_attr), + ) + + if r != 0 { + return err + } else { + return nil + } +} + +func (mfd MapFD) bpf_lookup_elem(key *int, value *uint64) error { + /* + int bpf_lookup_elem(int fd, const void *key, void *value) + { + union bpf_attr attr = { + .map_fd = fd, + .key = (__u64) (unsigned long) key, + .value = (__u64) (unsigned long) value, + }; + + return bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); + } + */ + + lookup_attr := struct { + map_fd MapFD + key uint64 + value uint64 + }{ + map_fd: mfd, + key: uint64(uintptr(unsafe.Pointer(key))), + value: uint64(uintptr(unsafe.Pointer(value))), + } + + r, _, err := syscall.Syscall( + BPF_SYSCALL, + uintptr(BPF_MAP_LOOKUP_ELEM), + uintptr(unsafe.Pointer(&lookup_attr)), + unsafe.Sizeof(lookup_attr), + ) + + if r != 0 { + return err + } + return nil +} + // GetMap gets the key/value pairs from the specified eBPF map as a Go map func (mfd MapFD) GetMap() (map[int]uint64, error) { retMap := make(map[int]uint64) - mapFDC := C.int(mfd) - keyC := C.int(0) - nextKeyC := C.int(0) - valC := C.uint64_t(0) + var ( + key, next int + value uint64 + ) for { - r, errno := C.bpf_get_next_key(mapFDC, unsafe.Pointer(&keyC), unsafe.Pointer(&nextKeyC)) - if r == -1 { - if errno == syscall.ENOENT { + err := mfd.bpf_get_next_key(&key, &next) + if err != nil { + if err == syscall.ENOENT { // The provided key was the last element. We're done iterating. return retMap, nil } - return nil, fmt.Errorf("bpf_get_next_key failed with errno %d", errno) + return nil, fmt.Errorf("bpf_get_next_key failed with error %v", err) } - r = C.bpf_lookup_elem(mapFDC, unsafe.Pointer(&nextKeyC), unsafe.Pointer(&valC)) - if r == -1 { - return nil, fmt.Errorf("bpf_lookup_elem failed") + err = mfd.bpf_lookup_elem(&next, &value) + if err != nil { + return nil, fmt.Errorf("bpf_lookup_elem failed with error %v", err) } - retMap[int(nextKeyC)] = uint64(valC) - keyC = nextKeyC + retMap[next] = value + + key = next } } +const ( + BPF_ANY = iota + BPF_NOEXIST + BPF_EXIST + BPF_F_LOCK +) + // Add puts the (key, value) into the eBPF map, only if the key does not exist // yet in the map. It returns an error otherwise. func (mfd MapFD) Add(key int, value uint64) error { - return mfd.updateElement(key, value, C.BPF_NOEXIST) + return mfd.updateElement(key, value, BPF_NOEXIST) } // Change changes the value to an existing key in the eBPF map. It returns an // error otherwise. func (mfd MapFD) Change(key int, value uint64) error { - return mfd.updateElement(key, value, C.BPF_EXIST) + return mfd.updateElement(key, value, BPF_EXIST) } // Set puts the (key, value) into the eBPF map. It will create or overwrite an // existing entry for that key. func (mfd MapFD) Set(key int, value uint64) error { - return mfd.updateElement(key, value, C.BPF_ANY) + return mfd.updateElement(key, value, BPF_ANY) } // updateElement is the low level wrapper to bpf_update_elem, used from Add(), // Set() and Change(). -func (mfd MapFD) updateElement(key int, value uint64, flag C.uint64_t) error { +func (mfd MapFD) updateElement(key int, value uint64, flag uint64) error { + /* + int bpf_update_elem(int fd, const void *key, const void *value, uint64_t flags) + { + union bpf_attr attr = { + .map_fd = fd, + .key = (__u64) (unsigned long) key, + .value = (__u64) (unsigned long) value, + .flags = flags, + }; - r, errno := C.bpf_update_elem(C.int(mfd), - unsafe.Pointer(&key), - unsafe.Pointer(&value), - flag) + return bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); + } + */ - if r == -1 { - return fmt.Errorf("bpf_update_elem failed with errno %d", errno) + update_attr := struct { + map_fd MapFD + key uint64 + value uint64 + flags uint64 + }{ + map_fd: mfd, + key: uint64(uintptr(unsafe.Pointer(&key))), + value: uint64(uintptr(unsafe.Pointer(&value))), + flags: flag, } + r, _, err := syscall.Syscall( + BPF_SYSCALL, + uintptr(BPF_MAP_UPDATE_ELEM), + uintptr(unsafe.Pointer(&update_attr)), + unsafe.Sizeof(update_attr), + ) + + if r != 0 || err != 0 { + return fmt.Errorf("couldn't update element: %s", err.Error()) + } return nil } diff --git a/GetRuntimeAddresses/ebpf/ebpf_cgo.go b/GetRuntimeAddresses/ebpf/ebpf_cgo.go new file mode 100644 index 0000000..4398efc --- /dev/null +++ b/GetRuntimeAddresses/ebpf/ebpf_cgo.go @@ -0,0 +1,151 @@ +// +build withcgo + +package ebpf + +/* +#include +#include +#include +#include +#include + +// See the bpf man page for details on the following functions + +static int bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size) +{ + return syscall(__NR_bpf, cmd, attr, size); +} + +static int bpf_create_map( + enum bpf_map_type map_type, unsigned int key_size, unsigned int value_size, + unsigned int max_entries) +{ + union bpf_attr attr = { + .map_type = map_type, + .key_size = key_size, + .value_size = value_size, + .max_entries = max_entries}; + + return bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); +} + +int bpf_get_next_key(int fd, const void *key, void *next_key) +{ + union bpf_attr attr = { + .map_fd = fd, + .key = (__u64) (unsigned long) key, + .next_key = (__u64) (unsigned long) next_key}; + + return bpf(BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr)); +} + + +int bpf_lookup_elem(int fd, const void *key, void *value) +{ + union bpf_attr attr = { + .map_fd = fd, + .key = (__u64) (unsigned long) key, + .value = (__u64) (unsigned long) value, + }; + + return bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); +} + +int bpf_update_elem(int fd, const void *key, const void *value, uint64_t flags) +{ + union bpf_attr attr = { + .map_fd = fd, + .key = (__u64) (unsigned long) key, + .value = (__u64) (unsigned long) value, + .flags = flags, + }; + + return bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); +} + +*/ +import "C" +import ( + "fmt" + "syscall" + "unsafe" +) + +// MapFD is a file descriptor representing a eBPF map +type MapFD int + +// CreateMap creates an eBPF map from int->uint64. The file descriptor of the +// created map is returned. +func CreateMap() (MapFD, error) { + + r, errno := C.bpf_create_map(C.BPF_MAP_TYPE_HASH, 4, 8, 64) + + if r == -1 { + return MapFD(r), fmt.Errorf("bpf_create_map with errno %d", errno) + } + + return MapFD(r), nil +} + +// GetMap gets the key/value pairs from the specified eBPF map as a Go map +func (mfd MapFD) GetMap() (map[int]uint64, error) { + retMap := make(map[int]uint64) + + mapFDC := C.int(mfd) + keyC := C.int(0) + nextKeyC := C.int(0) + valC := C.uint64_t(0) + + for { + r, errno := C.bpf_get_next_key(mapFDC, unsafe.Pointer(&keyC), unsafe.Pointer(&nextKeyC)) + if r == -1 { + if errno == syscall.ENOENT { + // The provided key was the last element. We're done iterating. + return retMap, nil + } + return nil, fmt.Errorf("bpf_get_next_key failed with errno %d", errno) + } + + r = C.bpf_lookup_elem(mapFDC, unsafe.Pointer(&nextKeyC), unsafe.Pointer(&valC)) + if r == -1 { + return nil, fmt.Errorf("bpf_lookup_elem failed") + } + retMap[int(nextKeyC)] = uint64(valC) + + keyC = nextKeyC + } +} + +// Add puts the (key, value) into the eBPF map, only if the key does not exist +// yet in the map. It returns an error otherwise. +func (mfd MapFD) Add(key int, value uint64) error { + return mfd.updateElement(key, value, C.BPF_NOEXIST) +} + +// Change changes the value to an existing key in the eBPF map. It returns an +// error otherwise. +func (mfd MapFD) Change(key int, value uint64) error { + return mfd.updateElement(key, value, C.BPF_EXIST) +} + +// Set puts the (key, value) into the eBPF map. It will create or overwrite an +// existing entry for that key. +func (mfd MapFD) Set(key int, value uint64) error { + return mfd.updateElement(key, value, C.BPF_ANY) +} + +// updateElement is the low level wrapper to bpf_update_elem, used from Add(), +// Set() and Change(). +func (mfd MapFD) updateElement(key int, value uint64, flag C.uint64_t) error { + + r, errno := C.bpf_update_elem(C.int(mfd), + unsafe.Pointer(&key), + unsafe.Pointer(&value), + flag) + + if r == -1 { + return fmt.Errorf("bpf_update_elem failed with errno %d", errno) + } + + return nil +}