Skip to content

Commit

Permalink
New CreateMap API implementing bpf_map_create, Type/name accessors (#138
Browse files Browse the repository at this point in the history
)

* Add bpf_map_create() API and selftest

Signed-off-by: grantseltzer <grantseltzer@gmail.com>

* Add API functions for set/get of map type, and map names

* Add comment further explaining use of CreateMap

Signed-off-by: grantseltzer <grantseltzer@gmail.com>
  • Loading branch information
grantseltzer committed Mar 23, 2022
1 parent 5aa6aaa commit 5ced2a6
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 0 deletions.
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)
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)))
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)
{
return 0;
}

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

import "C"

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

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

/*
CreateMap uses `bpf_map_create()`, a 'low-level' API in libbpf
As such, it does not have access to the higher level APIs in
libbpf, which are denoted by starting with `bpf_map__*`.
As such, you can use bpf_map_create() to populate arrays of maps
in userspace, then iterate over those arrays on the bpf side, and
use them as a normal map there as those operations only require
a file descriptor.
You can update values in the map from userspace, but currently
can't retrieve values outright.
For example:
https://elixir.bootlin.com/linux/latest/source/samples/bpf/fds_example.c
https://elixir.bootlin.com/linux/latest/source/tools/testing/selftests/bpf/test_maps.c
https://lore.kernel.org/bpf/CAO658oWagXsQDeFtRA2vZBzov7cwwVNTs5nHE9fMGrMOs6bbpQ@mail.gmail.com/
*/

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

0 comments on commit 5ced2a6

Please sign in to comment.