305 lines
6.5 KiB
Go
305 lines
6.5 KiB
Go
// +build !withcgo
|
|
|
|
package ebpf
|
|
|
|
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 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};
|
|
|
|
return bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
|
|
}
|
|
*/
|
|
|
|
create_attr := struct {
|
|
map_type uint32
|
|
key_size uint32
|
|
value_size uint32
|
|
max_entries uint32
|
|
}{
|
|
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)
|
|
|
|
var (
|
|
key, next int
|
|
value uint64
|
|
)
|
|
|
|
for {
|
|
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 error %v", err)
|
|
}
|
|
|
|
err = mfd.bpf_lookup_elem(&next, &value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bpf_lookup_elem failed with error %v", err)
|
|
}
|
|
|
|
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, 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, 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, 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 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,
|
|
};
|
|
|
|
return bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
|
|
}
|
|
*/
|
|
|
|
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
|
|
}
|