Skip to content
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

New CreateMap API implementing bpf_map_create, Type/name accessors #138

Merged
merged 3 commits into from Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
153 changes: 153 additions & 0 deletions libbpfgo.go
Expand Up @@ -124,6 +124,79 @@ type BPFMap struct {
module *Module
}

type MapType uint32

const (
MapTypeUnspec MapType = iota
MapTypeHash
MapTypeArray
MapTypeProgArray
MapTypePerfEventArray
MapTypePerCPUHash
MapTypePerCPUArray
MapTypeStackTrace
MapTypeCgroupArray
MapTypeLRUHash
MapTypeLRUPerCPUHash
MapTypeLPMTrie
MapTypeArrayOfMaps
MapTypeHashOfMaps
MapTypeDevMap
MapTypeSockMap
MapTypeCPUMap
MapTypeXSKMap
MapTypeSockHash
MapTypeCgroupStorage
MapTypeReusePortSockArray
MapTypePerCPUCgroupStorage
MapTypeQueue
MapTypeStack
MapTypeSKStorage
MapTypeDevmapHash
MapTypeStructOps
MapTypeRingbuf
MapTypeInodeStorage
MapTypeTaskStorage
MapTypeBloomFilter
)

func (m MapType) String() string {
x := map[MapType]string{
MapTypeUnspec: "BPF_MAP_TYPE_UNSPEC",
MapTypeHash: "BPF_MAP_TYPE_HASH",
MapTypeArray: "BPF_MAP_TYPE_ARRAY",
MapTypeProgArray: "BPF_MAP_TYPE_PROG_ARRAY",
MapTypePerfEventArray: "BPF_MAP_TYPE_PERF_EVENT_ARRAY",
MapTypePerCPUHash: "BPF_MAP_TYPE_PERCPU_HASH",
MapTypePerCPUArray: "BPF_MAP_TYPE_PERCPU_ARRAY",
MapTypeStackTrace: "BPF_MAP_TYPE_STACK_TRACE",
MapTypeCgroupArray: "BPF_MAP_TYPE_CGROUP_ARRAY",
MapTypeLRUHash: "BPF_MAP_TYPE_LRU_HASH",
MapTypeLRUPerCPUHash: "BPF_MAP_TYPE_LRU_PERCPU_HASH",
MapTypeLPMTrie: "BPF_MAP_TYPE_LPM_TRIE",
MapTypeArrayOfMaps: "BPF_MAP_TYPE_ARRAY_OF_MAPS",
MapTypeHashOfMaps: "BPF_MAP_TYPE_HASH_OF_MAPS",
MapTypeDevMap: "BPF_MAP_TYPE_DEVMAP",
MapTypeSockMap: "BPF_MAP_TYPE_SOCKMAP",
MapTypeCPUMap: "BPF_MAP_TYPE_CPUMAP",
MapTypeXSKMap: "BPF_MAP_TYPE_XSKMAP",
MapTypeSockHash: "BPF_MAP_TYPE_SOCKHASH",
MapTypeCgroupStorage: "BPF_MAP_TYPE_CGROUP_STORAGE",
MapTypeReusePortSockArray: "BPF_MAP_TYPE_REUSEPORT_SOCKARRAY",
MapTypePerCPUCgroupStorage: "BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE",
MapTypeQueue: "BPF_MAP_TYPE_QUEUE",
MapTypeStack: "BPF_MAP_TYPE_STACK",
MapTypeSKStorage: "BPF_MAP_TYPE_SK_STORAGE",
MapTypeDevmapHash: "BPF_MAP_TYPE_DEVMAP_HASH",
MapTypeStructOps: "BPF_MAP_TYPE_STRUCT_OPS",
MapTypeRingbuf: "BPF_MAP_TYPE_RINGBUF",
MapTypeInodeStorage: "BPF_MAP_TYPE_INODE_STORAGE",
MapTypeTaskStorage: "BPF_MAP_TYPE_TASK_STORAGE",
MapTypeBloomFilter: "BPF_MAP_TYPE_BLOOM_FILTER",
}
return x[m]
}

type BPFProg struct {
name string
prog *C.struct_bpf_program
Expand Down Expand Up @@ -325,6 +398,62 @@ func (m *Module) BPFLoadObject() error {
return nil
}

// BPFMapCreateOpts mirrors the C structure bpf_map_create_opts
type BPFMapCreateOpts struct {
Size uint64
BtfFD uint32
BtfKeyTypeID uint32
BtfValueTypeID uint32
BtfVmlinuxValueTypeID uint32
InnerMapFD uint32
MapFlags uint32
MapExtra uint64
NumaNode uint32
MapIfIndex uint32
}

func bpfMapCreateOptsToC(createOpts *BPFMapCreateOpts) *C.struct_bpf_map_create_opts {
if createOpts == nil {
return nil
}
opts := C.struct_bpf_map_create_opts{}
opts.sz = C.ulong(createOpts.Size)
opts.btf_fd = C.uint(createOpts.BtfFD)
opts.btf_key_type_id = C.uint(createOpts.BtfKeyTypeID)
opts.btf_value_type_id = C.uint(createOpts.BtfValueTypeID)
opts.btf_vmlinux_value_type_id = C.uint(createOpts.BtfVmlinuxValueTypeID)
opts.inner_map_fd = C.uint(createOpts.InnerMapFD)
opts.map_flags = C.uint(createOpts.MapFlags)
opts.map_extra = C.ulonglong(createOpts.MapExtra)
opts.numa_node = C.uint(createOpts.NumaNode)
opts.map_ifindex = C.uint(createOpts.MapIfIndex)

return &opts
}

// CreateMap creates a BPF map from userspace. This can be used for populating
// BPF array of maps or hash of maps. However, this function uses a low-level
// libbpf API; maps created in this way do not conform to libbpf map formats,
// and therefore do not have access to libbpf high level bpf_map__* APIS
// which causes different behavior from maps created in the kernel side code
//
// See usage of `bpf_map_create()` in kernel selftests for more info
func CreateMap(mapType MapType, mapName string, keySize, valueSize, maxEntries int, opts *BPFMapCreateOpts) (*BPFMap, error) {
cs := C.CString(mapName)
fdOrError := C.bpf_map_create(uint32(mapType), cs, C.uint(keySize), C.uint(valueSize), C.uint(maxEntries), bpfMapCreateOptsToC(opts))
C.free(unsafe.Pointer(cs))
if fdOrError < 0 {
return nil, fmt.Errorf("could not create map: %w", syscall.Errno(-fdOrError))
}

return &BPFMap{
name: mapName,
fd: fdOrError,
module: nil,
bpfMap: nil,
}, nil
}

func (m *Module) GetMap(mapName string) (*BPFMap, error) {
cs := C.CString(mapName)
bpfMap, errno := C.bpf_object__find_map_by_name(m.obj, cs)
Expand All @@ -341,6 +470,30 @@ func (m *Module) GetMap(mapName string) (*BPFMap, error) {
}, nil
}

func (b *BPFMap) Name() string {
cs := C.bpf_map__name(b.bpfMap)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoting from CreateMap above:

// and therefore do not have access to libbpf high level bpf_map__* APIS

So, this won't work for maps created with CreateMap, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct, the struct bpf_map is NULL which bpf_map__name() uses as it's parameter. CreateMap only provides a file descriptor for the map.

if cs == nil {
return ""
}
s := C.GoString(cs)
return s
}

func (b *BPFMap) Type() MapType {
return MapType(C.bpf_map__type(b.bpfMap))
}

// SetType is used to set the type of a bpf map that isn't associated
// with a file descriptor already. If the map is already associated
// with a file descriptor the libbpf API will return error code EBUSY
func (b *BPFMap) SetType(mapType MapType) error {
errC := C.bpf_map__set_type(b.bpfMap, C.enum_bpf_map_type(int(mapType)))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

if errC != 0 {
return fmt.Errorf("could not set bpf map type: %w", syscall.Errno(-errC))
}
return nil
}

func (b *BPFMap) Pin(pinPath string) error {
path := C.CString(pinPath)
errC := C.bpf_map__pin(b.bpfMap, path)
Expand Down
1 change: 1 addition & 0 deletions selftest/create-map/Makefile
7 changes: 7 additions & 0 deletions selftest/create-map/go.mod
@@ -0,0 +1,7 @@
module github.com/aquasecurity/libbpfgo/selftest/map-pin-info

go 1.16

require github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0

replace github.com/aquasecurity/libbpfgo => ../../
11 changes: 11 additions & 0 deletions selftest/create-map/go.sum
@@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
11 changes: 11 additions & 0 deletions selftest/create-map/main.bpf.c
@@ -0,0 +1,11 @@
//+build ignore
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

SEC("kprobe/sys_execve")
int kprobe__sys_execve(struct pt_regs *ctx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// CreateMap creates a BPF map from userspace. This can be used for populating
// BPF array of maps or hash of maps. However, this function uses a low-level
// libbpf API; maps created in this way do not conform to libbpf map formats,
// and therefore do not have access to libbpf high level bpf_map__* APIS
// which causes different behavior from maps created in the kernel side code

Considering the comment above, is it possible to add links or example APIs here to demonstrate accessing the maps that are created in the user space?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pushed a comment into the selftest with further explanation. If you want to try using this in Parca and are up for working on it together please tag me in the issue/PR and i'd gladly help figure it out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 Thanks!

{
return 0;
}

char LICENSE[] SEC("license") = "Dual BSD/GPL";
40 changes: 40 additions & 0 deletions selftest/create-map/main.go
@@ -0,0 +1,40 @@
package main

import "C"

import (
"fmt"
"log"
"os"
"unsafe"

"github.com/aquasecurity/libbpfgo"
bpf "github.com/aquasecurity/libbpfgo"
)

func main() {
bpfModule, err := bpf.NewModuleFromFile("main.bpf.o")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(-1)
}
defer bpfModule.Close()

bpfModule.BPFLoadObject()
opts := bpf.BPFMapCreateOpts{}
opts.Size = uint64(unsafe.Sizeof(opts))

m, err := libbpfgo.CreateMap(libbpfgo.MapTypeHash, "foobar", 4, 4, 420, nil)
if err != nil {
log.Fatal(err)
}

key1 := uint32(1)
value1 := uint32(55)
key1Unsafe := unsafe.Pointer(&key1)
value1Unsafe := unsafe.Pointer(&value1)
err = m.Update(key1Unsafe, value1Unsafe)
if err != nil {
log.Fatal(err)
}
}
1 change: 1 addition & 0 deletions selftest/create-map/run.sh
11 changes: 11 additions & 0 deletions selftest/map-update/main.go
Expand Up @@ -11,6 +11,7 @@ import (
"fmt"
"syscall"

"github.com/aquasecurity/libbpfgo"
bpf "github.com/aquasecurity/libbpfgo"
)

Expand Down Expand Up @@ -58,6 +59,16 @@ func main() {
os.Exit(-1)
}

if testerMap.Name() != "tester" {
fmt.Fprintln(os.Stderr, "wrong map")
os.Exit(-1)
}

if testerMap.Type() != libbpfgo.MapTypeHash {
fmt.Fprintln(os.Stderr, "wrong map type")
os.Exit(-1)
}

key1 := uint32(1)
value1 := struct{ x int }{50}
key1Unsafe := unsafe.Pointer(&key1)
Expand Down