diff --git a/btf.go b/btf.go new file mode 100644 index 00000000..8a60abb6 --- /dev/null +++ b/btf.go @@ -0,0 +1,26 @@ +package libbpfgo + +/* +#cgo LDFLAGS: -lelf -lz +#include "libbpfgo.h" +*/ +import "C" + +import ( + "fmt" + "syscall" +) + +// +// BTF (low-level API) +// + +// GetBTFFDByID returns a file descriptor for the BTF with the given ID. +func GetBTFFDByID(id uint32) (int, error) { + fdC := C.bpf_btf_get_fd_by_id(C.uint(id)) + if fdC < 0 { + return int(fdC), fmt.Errorf("could not find BTF id %d: %w", id, syscall.Errno(-fdC)) + } + + return int(fdC), nil +} diff --git a/map-common.go b/map-common.go index d16bb468..73062833 100644 --- a/map-common.go +++ b/map-common.go @@ -125,6 +125,16 @@ type BPFMapInfo struct { MapExtra uint64 } +// GetMapFDByID returns a file descriptor for the map with the given ID. +func GetMapFDByID(id uint32) (int, error) { + fdC := C.bpf_map_get_fd_by_id(C.uint(id)) + if fdC < 0 { + return int(fdC), fmt.Errorf("could not find map id %d: %w", id, syscall.Errno(-fdC)) + } + + return int(fdC), nil +} + // GetMapInfoByFD returns the BPFMapInfo for the map with the given file descriptor. func GetMapInfoByFD(fd int) (*BPFMapInfo, error) { var info C.struct_bpf_map_info diff --git a/map-low.go b/map-low.go index 2e7e69a1..c31ff292 100644 --- a/map-low.go +++ b/map-low.go @@ -81,6 +81,24 @@ func CreateMap(mapType MapType, mapName string, keySize, valueSize, maxEntries i }, nil } +// GetMapByID returns a BPFMapLow instance for the map with the given ID. +func GetMapByID(id uint32) (*BPFMapLow, error) { + fd, err := GetMapFDByID(id) + if err != nil { + return nil, err + } + + info, err := GetMapInfoByFD(fd) + if err != nil { + return nil, err + } + + return &BPFMapLow{ + fd: fd, + info: info, + }, nil +} + // // BPFMapLow Specs // diff --git a/map.go b/map.go index 8266665a..8a1b66fc 100644 --- a/map.go +++ b/map.go @@ -168,13 +168,13 @@ func (m *BPFMap) SetValueSize(size uint32) error { return nil } -// TODO: implement `bpf_map__btf_key_type_id` wrapper -// func (m *BPFMap) BTFKeyTypeID() uint32 { -// } +func (m *BPFMap) BTFKeyTypeID() uint32 { + return uint32(C.bpf_map__btf_key_type_id(m.bpfMap)) +} -// TODO: implement `bpf_map__btf_value_type_id` wrapper -// func (m *BPFMap) BTFValueTypeID() uint32 { -// } +func (m *BPFMap) BTFValueTypeID() uint32 { + return uint32(C.bpf_map__btf_value_type_id(m.bpfMap)) +} func (m *BPFMap) IfIndex() uint32 { return uint32(C.bpf_map__ifindex(m.bpfMap)) @@ -279,13 +279,64 @@ func (m *BPFMap) Unpin(pinPath string) error { // BPFMap Map of Maps // -// TODO: implement `bpf_map__inner_map` wrapper -// func (m *BPFMap) InnerMap() *BPFMap { -// } +// InnerMap retrieves the inner map prototype information associated with a +// BPFMap that represents a map of maps. +// +// NOTE: It must be called before the module is loaded, since it is a prototype +// destroyed right after the outer map is created. +// +// Reference: +// https://lore.kernel.org/bpf/20200429002739.48006-4-andriin@fb.com/ +func (m *BPFMap) InnerMapInfo() (*BPFMapInfo, error) { + innerMapC, errno := C.bpf_map__inner_map(m.bpfMap) + if innerMapC == nil { + return nil, fmt.Errorf("failed to get inner map for %s: %w", m.Name(), errno) + } -// TODO: implement `bpf_map__set_inner_map_fd` wrapper -// func (m *BPFMap) SetInnerMapFD(innerMapFD int) error { -// } + innerBPFMap := &BPFMap{ + bpfMap: innerMapC, + module: m.module, + } + + return &BPFMapInfo{ + // as it is a prototype, some values are not available + Type: innerBPFMap.Type(), + ID: 0, + KeySize: uint32(innerBPFMap.KeySize()), + ValueSize: uint32(innerBPFMap.ValueSize()), + MaxEntries: innerBPFMap.MaxEntries(), + MapFlags: uint32(innerBPFMap.MapFlags()), + Name: innerBPFMap.Name(), + IfIndex: innerBPFMap.IfIndex(), + BTFVmlinuxValueTypeID: 0, + NetnsDev: 0, + NetnsIno: 0, + BTFID: 0, + BTFKeyTypeID: innerBPFMap.BTFKeyTypeID(), + BTFValueTypeID: innerBPFMap.BTFValueTypeID(), + MapExtra: innerBPFMap.MapExtra(), + }, nil +} + +// SetInnerMap configures the inner map prototype for a BPFMap that represents +// a map of maps. +// +// This function accepts the file descriptor of another map, which will serve as +// a prototype. +// +// NOTE: It must be called before the module is loaded. +func (m *BPFMap) SetInnerMap(templateMapFD int) error { + if templateMapFD < 0 { + return fmt.Errorf("invalid inner map fd %d", templateMapFD) + } + + retC := C.bpf_map__set_inner_map_fd(m.bpfMap, C.int(templateMapFD)) + if retC < 0 { + return fmt.Errorf("failed to set inner map for %s: %w", m.Name(), syscall.Errno(-retC)) + } + + return nil +} // // BPFMap Operations diff --git a/selftest/getbtffdbyid/Makefile b/selftest/getbtffdbyid/Makefile new file mode 120000 index 00000000..d981720c --- /dev/null +++ b/selftest/getbtffdbyid/Makefile @@ -0,0 +1 @@ +../common/Makefile \ No newline at end of file diff --git a/selftest/getbtffdbyid/go.mod b/selftest/getbtffdbyid/go.mod new file mode 100644 index 00000000..1fd0933d --- /dev/null +++ b/selftest/getbtffdbyid/go.mod @@ -0,0 +1,7 @@ +module github.com/aquasecurity/libbpfgo/selftest/map-update + +go 1.18 + +require github.com/aquasecurity/libbpfgo v0.4.7-libbpf-1.2.0-b2e29a1 + +replace github.com/aquasecurity/libbpfgo => ../../ diff --git a/selftest/getbtffdbyid/go.sum b/selftest/getbtffdbyid/go.sum new file mode 100644 index 00000000..c60af667 --- /dev/null +++ b/selftest/getbtffdbyid/go.sum @@ -0,0 +1,4 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/selftest/getbtffdbyid/main.bpf.c b/selftest/getbtffdbyid/main.bpf.c new file mode 100644 index 00000000..cff8dace --- /dev/null +++ b/selftest/getbtffdbyid/main.bpf.c @@ -0,0 +1,14 @@ +//+build ignore + +#include + +#include + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, __u32); +} inner_array_proto SEC(".maps"); + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/selftest/getbtffdbyid/main.go b/selftest/getbtffdbyid/main.go new file mode 100644 index 00000000..e5ed9b9e --- /dev/null +++ b/selftest/getbtffdbyid/main.go @@ -0,0 +1,60 @@ +package main + +import "C" + +import ( + "log" + "unsafe" + + bpf "github.com/aquasecurity/libbpfgo" +) + +func main() { + bpfModule, err := bpf.NewModuleFromFile("main.bpf.o") + if err != nil { + log.Fatal(err) + } + defer bpfModule.Close() + + err = bpfModule.BPFLoadObject() + if err != nil { + log.Fatal(err) + } + + // Use the "inner_array_proto" map BTF ID to create a new map with the same + // BTF type. + innerArrayProto, err := bpfModule.GetMap("inner_array_proto") + if err != nil { + log.Fatal(err) + } + + optsProto, err := bpf.GetMapInfoByFD(innerArrayProto.FileDescriptor()) + if err != nil { + log.Fatal(err) + } + + // The "inner_array_proto" map is a BTF map, so its ID can be used to create + // a new map with the same BTF type. + btfFD, err := bpf.GetBTFFDByID(optsProto.BTFID) + if err != nil { + log.Fatal(err) + } + + createOpts := &bpf.BPFMapCreateOpts{ + BTFFD: uint32(btfFD), + } + innerArray, err := bpf.CreateMap(bpf.MapTypeArray, "inner_array", 4, 4, 1, createOpts) + if err != nil { + log.Fatal(err) + } + + // Save an element in the "inner_array" map. + key := uint32(0) // index 0 + keyUnsafe := unsafe.Pointer(&key) + value := uint32(191711) + valueUnsafe := unsafe.Pointer(&value) + err = innerArray.Update(keyUnsafe, valueUnsafe) + if err != nil { + log.Fatal(err) + } +} diff --git a/selftest/getbtffdbyid/run.sh b/selftest/getbtffdbyid/run.sh new file mode 120000 index 00000000..aee911b2 --- /dev/null +++ b/selftest/getbtffdbyid/run.sh @@ -0,0 +1 @@ +../common/run.sh \ No newline at end of file diff --git a/selftest/map-getbyid/Makefile b/selftest/map-getbyid/Makefile new file mode 120000 index 00000000..d981720c --- /dev/null +++ b/selftest/map-getbyid/Makefile @@ -0,0 +1 @@ +../common/Makefile \ No newline at end of file diff --git a/selftest/map-getbyid/go.mod b/selftest/map-getbyid/go.mod new file mode 100644 index 00000000..1fd0933d --- /dev/null +++ b/selftest/map-getbyid/go.mod @@ -0,0 +1,7 @@ +module github.com/aquasecurity/libbpfgo/selftest/map-update + +go 1.18 + +require github.com/aquasecurity/libbpfgo v0.4.7-libbpf-1.2.0-b2e29a1 + +replace github.com/aquasecurity/libbpfgo => ../../ diff --git a/selftest/map-getbyid/go.sum b/selftest/map-getbyid/go.sum new file mode 100644 index 00000000..c60af667 --- /dev/null +++ b/selftest/map-getbyid/go.sum @@ -0,0 +1,4 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/selftest/map-getbyid/main.bpf.c b/selftest/map-getbyid/main.bpf.c new file mode 100644 index 00000000..ba944d74 --- /dev/null +++ b/selftest/map-getbyid/main.bpf.c @@ -0,0 +1,14 @@ +//+build ignore + +#include + +#include + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, u32); + __type(value, u32); + __uint(max_entries, 1); +} tester SEC(".maps"); + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/selftest/map-getbyid/main.go b/selftest/map-getbyid/main.go new file mode 100644 index 00000000..9a283b85 --- /dev/null +++ b/selftest/map-getbyid/main.go @@ -0,0 +1,85 @@ +package main + +import "C" + +import ( + "encoding/binary" + "log" + "unsafe" + + bpf "github.com/aquasecurity/libbpfgo" +) + +func main() { + bpfModule, err := bpf.NewModuleFromFile("main.bpf.o") + if err != nil { + log.Fatal(err) + } + defer bpfModule.Close() + + bpfModule.BPFLoadObject() + + testerMap, err := bpfModule.GetMap("tester") + if err != nil { + log.Fatal(err) + } + + // Get info about the "tester" map + infoTester, err := bpf.GetMapInfoByFD(testerMap.FileDescriptor()) + if err != nil { + log.Fatal(err) + } + + // Get a new BPFMapLow object pointing to the "tester" map + testerMapLow, err := bpf.GetMapByID(infoTester.ID) + if err != nil { + log.Fatal(err) + } + + if testerMapLow.Name() != testerMap.Name() { + log.Fatal("Names do not match") + } + if testerMapLow.Type() != testerMap.Type() { + log.Fatal("Types do not match") + } + if testerMapLow.MaxEntries() != testerMap.MaxEntries() { + log.Fatal("Max entries do not match") + } + if testerMapLow.KeySize() != testerMap.KeySize() { + log.Fatal("Key sizes do not match") + } + if testerMapLow.ValueSize() != testerMap.ValueSize() { + log.Fatal("Value sizes do not match") + } + + // Save a value in the "tester" map using the original BPFMap object + key1 := uint32(11) + value1 := uint32(1917) + key1Unsafe := unsafe.Pointer(&key1) + value1Unsafe := unsafe.Pointer(&value1) + err = testerMap.Update(key1Unsafe, value1Unsafe) + if err != nil { + log.Fatal(err) + } + + // Get the value from the "tester" map using the new BPFMapLow object + v, err := testerMapLow.GetValue(key1Unsafe) + if err != nil { + log.Fatal(err) + } + if endian().Uint32(v) != value1 { + log.Fatal("Value mismatch") + } +} + +func endian() binary.ByteOrder { + var i int32 = 0x01020304 + u := unsafe.Pointer(&i) + pb := (*byte)(u) + b := *pb + if b == 0x04 { + return binary.LittleEndian + } + + return binary.BigEndian +} diff --git a/selftest/map-getbyid/run.sh b/selftest/map-getbyid/run.sh new file mode 120000 index 00000000..aee911b2 --- /dev/null +++ b/selftest/map-getbyid/run.sh @@ -0,0 +1 @@ +../common/run.sh \ No newline at end of file diff --git a/selftest/map-getfdbyid/Makefile b/selftest/map-getfdbyid/Makefile new file mode 120000 index 00000000..d981720c --- /dev/null +++ b/selftest/map-getfdbyid/Makefile @@ -0,0 +1 @@ +../common/Makefile \ No newline at end of file diff --git a/selftest/map-getfdbyid/go.mod b/selftest/map-getfdbyid/go.mod new file mode 100644 index 00000000..1fd0933d --- /dev/null +++ b/selftest/map-getfdbyid/go.mod @@ -0,0 +1,7 @@ +module github.com/aquasecurity/libbpfgo/selftest/map-update + +go 1.18 + +require github.com/aquasecurity/libbpfgo v0.4.7-libbpf-1.2.0-b2e29a1 + +replace github.com/aquasecurity/libbpfgo => ../../ diff --git a/selftest/map-getfdbyid/go.sum b/selftest/map-getfdbyid/go.sum new file mode 100644 index 00000000..c60af667 --- /dev/null +++ b/selftest/map-getfdbyid/go.sum @@ -0,0 +1,4 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/selftest/map-getfdbyid/main.bpf.c b/selftest/map-getfdbyid/main.bpf.c new file mode 100644 index 00000000..ba944d74 --- /dev/null +++ b/selftest/map-getfdbyid/main.bpf.c @@ -0,0 +1,14 @@ +//+build ignore + +#include + +#include + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, u32); + __type(value, u32); + __uint(max_entries, 1); +} tester SEC(".maps"); + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/selftest/map-getfdbyid/main.go b/selftest/map-getfdbyid/main.go new file mode 100644 index 00000000..13401b20 --- /dev/null +++ b/selftest/map-getfdbyid/main.go @@ -0,0 +1,88 @@ +package main + +import "C" + +import ( + "log" + + bpf "github.com/aquasecurity/libbpfgo" +) + +func main() { + bpfModule, err := bpf.NewModuleFromFile("main.bpf.o") + if err != nil { + log.Fatal(err) + } + defer bpfModule.Close() + + bpfModule.BPFLoadObject() + + testerMap, err := bpfModule.GetMap("tester") + if err != nil { + log.Fatal(err) + } + + // Get info about the "tester" map + infoTester, err := bpf.GetMapInfoByFD(testerMap.FileDescriptor()) + if err != nil { + log.Fatal(err) + } + + // Get a new FD pointing to the "tester" map + newFD, err := bpf.GetMapFDByID(infoTester.ID) + if err != nil { + log.Fatal(err) + } + if newFD == testerMap.FileDescriptor() { + log.Fatal("New FD should be different from the old one") + } + + // Get info about the "tester" map again, this time using the new FD + infoNewFD, err := bpf.GetMapInfoByFD(newFD) + if err != nil { + log.Fatal(err) + } + + if infoTester.Type != infoNewFD.Type { + log.Fatal("Types do not match") + } + if infoTester.ID != infoNewFD.ID { + log.Fatal("IDs do not match") + } + if infoTester.KeySize != infoNewFD.KeySize { + log.Fatal("Key sizes do not match") + } + if infoTester.ValueSize != infoNewFD.ValueSize { + log.Fatal("Value sizes do not match") + } + if infoTester.MaxEntries != infoNewFD.MaxEntries { + log.Fatal("Max entries do not match") + } + if infoTester.MapFlags != infoNewFD.MapFlags { + log.Fatal("Map flags do not match") + } + if infoTester.Name != infoNewFD.Name { + log.Fatal("Names do not match") + } + if infoTester.IfIndex != infoNewFD.IfIndex { + log.Fatal("Ifindexes do not match") + } + if infoTester.NetnsDev != infoNewFD.NetnsDev { + log.Fatal("Netns do not match") + } + if infoTester.NetnsIno != infoNewFD.NetnsIno { + log.Fatal("Netns inodes do not match") + } + if infoTester.BTFID != infoNewFD.BTFID { + log.Fatal("BTF IDs do not match") + } + if infoTester.BTFKeyTypeID != infoNewFD.BTFKeyTypeID { + log.Fatal("BTF key type IDs do not match") + } + if infoTester.BTFValueTypeID != infoNewFD.BTFValueTypeID { + log.Fatal("BTF value type IDs do not match") + } + if infoTester.MapExtra != infoNewFD.MapExtra { + log.Fatal("Map extras do not match") + } +} diff --git a/selftest/map-getfdbyid/run.sh b/selftest/map-getfdbyid/run.sh new file mode 120000 index 00000000..aee911b2 --- /dev/null +++ b/selftest/map-getfdbyid/run.sh @@ -0,0 +1 @@ +../common/run.sh \ No newline at end of file diff --git a/selftest/map-innerinfo/Makefile b/selftest/map-innerinfo/Makefile new file mode 120000 index 00000000..d981720c --- /dev/null +++ b/selftest/map-innerinfo/Makefile @@ -0,0 +1 @@ +../common/Makefile \ No newline at end of file diff --git a/selftest/map-innerinfo/go.mod b/selftest/map-innerinfo/go.mod new file mode 100644 index 00000000..1fd0933d --- /dev/null +++ b/selftest/map-innerinfo/go.mod @@ -0,0 +1,7 @@ +module github.com/aquasecurity/libbpfgo/selftest/map-update + +go 1.18 + +require github.com/aquasecurity/libbpfgo v0.4.7-libbpf-1.2.0-b2e29a1 + +replace github.com/aquasecurity/libbpfgo => ../../ diff --git a/selftest/map-innerinfo/go.sum b/selftest/map-innerinfo/go.sum new file mode 100644 index 00000000..c60af667 --- /dev/null +++ b/selftest/map-innerinfo/go.sum @@ -0,0 +1,4 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/selftest/map-innerinfo/main.bpf.c b/selftest/map-innerinfo/main.bpf.c new file mode 100644 index 00000000..d8ad9cc9 --- /dev/null +++ b/selftest/map-innerinfo/main.bpf.c @@ -0,0 +1,29 @@ +//+build ignore + +#include + +#include + +// https://lore.kernel.org/bpf/20200429002739.48006-4-andriin@fb.com/ + +struct inner_map { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, __u32); +} inner_map1 SEC(".maps"), inner_map2 SEC(".maps"); + +struct outer_hash { + __uint(type, BPF_MAP_TYPE_HASH_OF_MAPS); + __uint(max_entries, 5); + __uint(key_size, sizeof(__u32)); + __array(values, struct inner_map); +} outer_hash SEC(".maps") = { + .values = + { + [0] = &inner_map2, + [4] = &inner_map1, + }, +}; + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/selftest/map-innerinfo/main.go b/selftest/map-innerinfo/main.go new file mode 100644 index 00000000..bd69a64a --- /dev/null +++ b/selftest/map-innerinfo/main.go @@ -0,0 +1,65 @@ +package main + +import "C" + +import ( + "log" + + bpf "github.com/aquasecurity/libbpfgo" +) + +func main() { + bpfModule, err := bpf.NewModuleFromFile("main.bpf.o") + if err != nil { + log.Fatal(err) + } + defer bpfModule.Close() + + outerHash, err := bpfModule.GetMap("outer_hash") + if err != nil { + log.Fatal(err) + } + + // Retrieve an inner map prototype information from the outer map. + innerInfo, err := outerHash.InnerMapInfo() + if err != nil { + log.Fatal(err) + } + + if innerInfo.Name != "outer_hash.inner" { + log.Fatal("inner prototype name should be 'outer_hash.inner'") + } + if innerInfo.Type != bpf.MapTypeArray { + log.Fatal("inner prototype type should be MapTypeArray") + } + if innerInfo.MaxEntries != 1 { + log.Fatal("inner prototype max entries should be 1") + } + if innerInfo.KeySize != 4 { + log.Fatal("inner prototype key size should be 4") + } + if innerInfo.ValueSize != 4 { + log.Fatal("inner prototype value size should be 4") + } + if innerInfo.MapFlags != 0 { + log.Fatal("inner prototype map flags should be 0") + } + if innerInfo.IfIndex != 0 { + log.Fatal("inner prototype ifindex should be 0") + } + if innerInfo.MapExtra != 0 { + log.Fatal("inner prototype map extra should be 0") + } + + err = bpfModule.BPFLoadObject() + if err != nil { + log.Fatal(err) + } + + // Attempting to get inner map prototype information after the + // object is loaded will fail. + _, err = outerHash.InnerMapInfo() + if err == nil { + log.Fatal("should fail after object is loaded") + } +} diff --git a/selftest/map-innerinfo/run.sh b/selftest/map-innerinfo/run.sh new file mode 120000 index 00000000..aee911b2 --- /dev/null +++ b/selftest/map-innerinfo/run.sh @@ -0,0 +1 @@ +../common/run.sh \ No newline at end of file diff --git a/selftest/map-of-maps-outer-high-inner-high/Makefile b/selftest/map-of-maps-outer-high-inner-high/Makefile new file mode 120000 index 00000000..d981720c --- /dev/null +++ b/selftest/map-of-maps-outer-high-inner-high/Makefile @@ -0,0 +1 @@ +../common/Makefile \ No newline at end of file diff --git a/selftest/map-of-maps-outer-high-inner-high/go.mod b/selftest/map-of-maps-outer-high-inner-high/go.mod new file mode 100644 index 00000000..1fd0933d --- /dev/null +++ b/selftest/map-of-maps-outer-high-inner-high/go.mod @@ -0,0 +1,7 @@ +module github.com/aquasecurity/libbpfgo/selftest/map-update + +go 1.18 + +require github.com/aquasecurity/libbpfgo v0.4.7-libbpf-1.2.0-b2e29a1 + +replace github.com/aquasecurity/libbpfgo => ../../ diff --git a/selftest/map-of-maps-outer-high-inner-high/go.sum b/selftest/map-of-maps-outer-high-inner-high/go.sum new file mode 100644 index 00000000..c60af667 --- /dev/null +++ b/selftest/map-of-maps-outer-high-inner-high/go.sum @@ -0,0 +1,4 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/selftest/map-of-maps-outer-high-inner-high/main.bpf.c b/selftest/map-of-maps-outer-high-inner-high/main.bpf.c new file mode 100644 index 00000000..d9c913a3 --- /dev/null +++ b/selftest/map-of-maps-outer-high-inner-high/main.bpf.c @@ -0,0 +1,26 @@ +//+build ignore + +#include + +#include + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, __u32); +} inner_array SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH_OF_MAPS); + __uint(max_entries, 1); + __type(key, __u32); + __array(values, typeof(inner_array)); +} outer_hash SEC(".maps") = { + .values = + { + [1917] = &inner_array, + }, +}; + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/selftest/map-of-maps-outer-high-inner-high/main.go b/selftest/map-of-maps-outer-high-inner-high/main.go new file mode 100644 index 00000000..4431d461 --- /dev/null +++ b/selftest/map-of-maps-outer-high-inner-high/main.go @@ -0,0 +1,81 @@ +package main + +import "C" + +import ( + "encoding/binary" + "log" + "unsafe" + + bpf "github.com/aquasecurity/libbpfgo" +) + +func main() { + bpfModule, err := bpf.NewModuleFromFile("main.bpf.o") + if err != nil { + log.Fatal(err) + } + defer bpfModule.Close() + + // Since the outer and inner map definitions are pre-allocated in the + // BPF object, we do not need to do anything before loading the object. + err = bpfModule.BPFLoadObject() + if err != nil { + log.Fatal(err) + } + + outerHash, err := bpfModule.GetMap("outer_hash") + if err != nil { + log.Fatal(err) + } + + innerArray, err := bpfModule.GetMap("inner_array") + if err != nil { + log.Fatal(err) + } + + // Retrieve the "inner_array" map ID from the "outer_hash" map, + // using the hash key 1917. + key1 := uint32(1917) + key1Unsafe := unsafe.Pointer(&key1) + innerMapIDBytes, err := outerHash.GetValue(key1Unsafe) + if err != nil { + log.Fatal(err) + } + + // Inner map ID retrieved from the outer map element. + innerMapID := endian().Uint32(innerMapIDBytes) + + // Retrieve the "inner_array" map Info. + innerMapInfo, error := bpf.GetMapInfoByFD(innerArray.FileDescriptor()) + if error != nil { + log.Fatal(error) + } + + // Check if the inner map ID retrieved from the outer map matches the + // inner map ID retrieved directly from the inner map. + if innerMapInfo.ID != innerMapID { + log.Fatal("inner map ID does not match") + } + + // Save an element in the "inner_array" map. + key1 = uint32(0) // index 0 + value1 := uint32(191711) + value1Unsafe := unsafe.Pointer(&value1) + err = innerArray.Update(key1Unsafe, value1Unsafe) + if err != nil { + log.Fatal(err) + } +} + +func endian() binary.ByteOrder { + var i int32 = 0x01020304 + u := unsafe.Pointer(&i) + pb := (*byte)(u) + b := *pb + if b == 0x04 { + return binary.LittleEndian + } + + return binary.BigEndian +} diff --git a/selftest/map-of-maps-outer-high-inner-high/run.sh b/selftest/map-of-maps-outer-high-inner-high/run.sh new file mode 120000 index 00000000..aee911b2 --- /dev/null +++ b/selftest/map-of-maps-outer-high-inner-high/run.sh @@ -0,0 +1 @@ +../common/run.sh \ No newline at end of file diff --git a/selftest/map-of-maps-outer-high-inner-low/Makefile b/selftest/map-of-maps-outer-high-inner-low/Makefile new file mode 120000 index 00000000..d981720c --- /dev/null +++ b/selftest/map-of-maps-outer-high-inner-low/Makefile @@ -0,0 +1 @@ +../common/Makefile \ No newline at end of file diff --git a/selftest/map-of-maps-outer-high-inner-low/go.mod b/selftest/map-of-maps-outer-high-inner-low/go.mod new file mode 100644 index 00000000..1fd0933d --- /dev/null +++ b/selftest/map-of-maps-outer-high-inner-low/go.mod @@ -0,0 +1,7 @@ +module github.com/aquasecurity/libbpfgo/selftest/map-update + +go 1.18 + +require github.com/aquasecurity/libbpfgo v0.4.7-libbpf-1.2.0-b2e29a1 + +replace github.com/aquasecurity/libbpfgo => ../../ diff --git a/selftest/map-of-maps-outer-high-inner-low/go.sum b/selftest/map-of-maps-outer-high-inner-low/go.sum new file mode 100644 index 00000000..c60af667 --- /dev/null +++ b/selftest/map-of-maps-outer-high-inner-low/go.sum @@ -0,0 +1,4 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/selftest/map-of-maps-outer-high-inner-low/main.bpf.c b/selftest/map-of-maps-outer-high-inner-low/main.bpf.c new file mode 100644 index 00000000..93f18558 --- /dev/null +++ b/selftest/map-of-maps-outer-high-inner-low/main.bpf.c @@ -0,0 +1,14 @@ +//+build ignore + +#include + +#include + +struct { + __uint(type, BPF_MAP_TYPE_HASH_OF_MAPS); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, __u32); +} outer_hash SEC(".maps"); + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/selftest/map-of-maps-outer-high-inner-low/main.go b/selftest/map-of-maps-outer-high-inner-low/main.go new file mode 100644 index 00000000..e996dd24 --- /dev/null +++ b/selftest/map-of-maps-outer-high-inner-low/main.go @@ -0,0 +1,99 @@ +package main + +import "C" + +import ( + "encoding/binary" + "log" + "unsafe" + + bpf "github.com/aquasecurity/libbpfgo" +) + +func main() { + bpfModule, err := bpf.NewModuleFromFile("main.bpf.o") + if err != nil { + log.Fatal(err) + } + defer bpfModule.Close() + + outerHash, err := bpfModule.GetMap("outer_hash") + if err != nil { + log.Fatal(err) + } + + innerArray, err := bpf.CreateMap(bpf.MapTypeArray, "inner_array", 4, 4, 1, nil) + if err != nil { + log.Fatal(err) + } + + // As the "outer_hash" map does not have an inner map prototype pre-allocated, + // an active map (from any origin) must be used as a template, by calling + // SetInnerMap() before the object is loaded, otherwise the BPF program will + // fail to load. The template map can be removed after the object is loaded. + err = outerHash.SetInnerMap(innerArray.FileDescriptor()) + if err != nil { + log.Fatal(err) + } + + err = bpfModule.BPFLoadObject() + if err != nil { + log.Fatal(err) + } + + // Save the inner map in the "outer_hash" map, using the hash key 1917. + // The value used to save the element is the the inner map file descriptor, + // however the actual saved value is the inner map ID. + key := uint32(1917) + keyUnsafe := unsafe.Pointer(&key) + value := uint32(innerArray.FileDescriptor()) // "inner_array" FD. + valueUnsafe := unsafe.Pointer(&value) + err = outerHash.Update(keyUnsafe, valueUnsafe) + if err != nil { + log.Fatal(err) + } + + // Retrieve the value of the previous saved element from the "outer_hash" map, + // using the hash key 1917. + key = uint32(1917) + keyUnsafe = unsafe.Pointer(&key) + innerMapIDBytes, err := outerHash.GetValue(keyUnsafe) // "inner_array" ID + if err != nil { + log.Fatal(err) + } + + // Inner map ID retrieved from the outer map element. + innerMapID := endian().Uint32(innerMapIDBytes) + + // Retrieve the "inner_array" map Info. + innerMapInfo, error := bpf.GetMapInfoByFD(innerArray.FileDescriptor()) + if error != nil { + log.Fatal(error) + } + + // Check if the inner map ID retrieved from the outer map matches the + // inner map ID retrieved directly from the inner map. + if innerMapInfo.ID != innerMapID { + log.Fatal("inner map ID does not match") + } + + // Save an element in the "inner_array" map. + key = uint32(0) // index 0 + value = uint32(191711) + err = innerArray.Update(keyUnsafe, valueUnsafe) + if err != nil { + log.Fatal(err) + } +} + +func endian() binary.ByteOrder { + var i int32 = 0x01020304 + u := unsafe.Pointer(&i) + pb := (*byte)(u) + b := *pb + if b == 0x04 { + return binary.LittleEndian + } + + return binary.BigEndian +} diff --git a/selftest/map-of-maps-outer-high-inner-low/run.sh b/selftest/map-of-maps-outer-high-inner-low/run.sh new file mode 120000 index 00000000..aee911b2 --- /dev/null +++ b/selftest/map-of-maps-outer-high-inner-low/run.sh @@ -0,0 +1 @@ +../common/run.sh \ No newline at end of file diff --git a/selftest/map-of-maps-outer-low-inner-low/Makefile b/selftest/map-of-maps-outer-low-inner-low/Makefile new file mode 120000 index 00000000..d981720c --- /dev/null +++ b/selftest/map-of-maps-outer-low-inner-low/Makefile @@ -0,0 +1 @@ +../common/Makefile \ No newline at end of file diff --git a/selftest/map-of-maps-outer-low-inner-low/go.mod b/selftest/map-of-maps-outer-low-inner-low/go.mod new file mode 100644 index 00000000..1fd0933d --- /dev/null +++ b/selftest/map-of-maps-outer-low-inner-low/go.mod @@ -0,0 +1,7 @@ +module github.com/aquasecurity/libbpfgo/selftest/map-update + +go 1.18 + +require github.com/aquasecurity/libbpfgo v0.4.7-libbpf-1.2.0-b2e29a1 + +replace github.com/aquasecurity/libbpfgo => ../../ diff --git a/selftest/map-of-maps-outer-low-inner-low/go.sum b/selftest/map-of-maps-outer-low-inner-low/go.sum new file mode 100644 index 00000000..c60af667 --- /dev/null +++ b/selftest/map-of-maps-outer-low-inner-low/go.sum @@ -0,0 +1,4 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/selftest/map-of-maps-outer-low-inner-low/main.bpf.c b/selftest/map-of-maps-outer-low-inner-low/main.bpf.c new file mode 100644 index 00000000..b734622c --- /dev/null +++ b/selftest/map-of-maps-outer-low-inner-low/main.bpf.c @@ -0,0 +1,7 @@ +//+build ignore + +#include + +#include + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/selftest/map-of-maps-outer-low-inner-low/main.go b/selftest/map-of-maps-outer-low-inner-low/main.go new file mode 100644 index 00000000..45de7188 --- /dev/null +++ b/selftest/map-of-maps-outer-low-inner-low/main.go @@ -0,0 +1,71 @@ +package main + +import "C" + +import ( + "encoding/binary" + "log" + "unsafe" + + bpf "github.com/aquasecurity/libbpfgo" +) + +func main() { + bpfModule, err := bpf.NewModuleFromFile("main.bpf.o") + if err != nil { + log.Fatal(err) + } + defer bpfModule.Close() + + err = bpfModule.BPFLoadObject() + if err != nil { + log.Fatal(err) + } + + innerArray, err := bpf.CreateMap(bpf.MapTypeArray, "inner_array", 4, 4, 1, nil) + if err != nil { + log.Fatal(err) + } + + // Create the "outer_hash" map, using the "inner_array" map as a prototype. + opts := bpf.BPFMapCreateOpts{ + InnerMapFD: uint32(innerArray.FileDescriptor()), + } + outerHash, err := bpf.CreateMap(bpf.MapTypeHash, "outer_hash", 4, 4, 1, &opts) + if err != nil { + log.Fatal(err) + } + + // Save the inner map in the "outer_hash" map, using the hash key 1917. + // The value used to save the element is the the inner map file descriptor, + // however the actual saved value is the inner map ID. + key1 := uint32(1917) + key1Unsafe := unsafe.Pointer(&key1) + value1 := uint32(innerArray.FileDescriptor()) // "inner_array" FD. + value1Unsafe := unsafe.Pointer(&value1) + err = outerHash.Update(key1Unsafe, value1Unsafe) + if err != nil { + log.Fatal(err) + } + + // Save an element in the "inner_array" map. + key1 = uint32(0) // index 0 + value1 = uint32(191711) + value1Unsafe = unsafe.Pointer(&value1) + err = innerArray.Update(key1Unsafe, value1Unsafe) + if err != nil { + log.Fatal(err) + } +} + +func endian() binary.ByteOrder { + var i int32 = 0x01020304 + u := unsafe.Pointer(&i) + pb := (*byte)(u) + b := *pb + if b == 0x04 { + return binary.LittleEndian + } + + return binary.BigEndian +} diff --git a/selftest/map-of-maps-outer-low-inner-low/run.sh b/selftest/map-of-maps-outer-low-inner-low/run.sh new file mode 120000 index 00000000..aee911b2 --- /dev/null +++ b/selftest/map-of-maps-outer-low-inner-low/run.sh @@ -0,0 +1 @@ +../common/run.sh \ No newline at end of file diff --git a/selftest/map-setinner/Makefile b/selftest/map-setinner/Makefile new file mode 120000 index 00000000..d981720c --- /dev/null +++ b/selftest/map-setinner/Makefile @@ -0,0 +1 @@ +../common/Makefile \ No newline at end of file diff --git a/selftest/map-setinner/go.mod b/selftest/map-setinner/go.mod new file mode 100644 index 00000000..1fd0933d --- /dev/null +++ b/selftest/map-setinner/go.mod @@ -0,0 +1,7 @@ +module github.com/aquasecurity/libbpfgo/selftest/map-update + +go 1.18 + +require github.com/aquasecurity/libbpfgo v0.4.7-libbpf-1.2.0-b2e29a1 + +replace github.com/aquasecurity/libbpfgo => ../../ diff --git a/selftest/map-setinner/go.sum b/selftest/map-setinner/go.sum new file mode 100644 index 00000000..c60af667 --- /dev/null +++ b/selftest/map-setinner/go.sum @@ -0,0 +1,4 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/selftest/map-setinner/main.bpf.c b/selftest/map-setinner/main.bpf.c new file mode 100644 index 00000000..3b77abb5 --- /dev/null +++ b/selftest/map-setinner/main.bpf.c @@ -0,0 +1,15 @@ +//+build ignore + +#include + +#include + +// Hash map of maps with no inner maps preallocated. +struct { + __uint(type, BPF_MAP_TYPE_HASH_OF_MAPS); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, __u32); +} outer_hash SEC(".maps"); + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/selftest/map-setinner/main.go b/selftest/map-setinner/main.go new file mode 100644 index 00000000..b406f6bc --- /dev/null +++ b/selftest/map-setinner/main.go @@ -0,0 +1,59 @@ +package main + +import "C" + +import ( + "log" + "syscall" + + bpf "github.com/aquasecurity/libbpfgo" +) + +func main() { + bpfModule, err := bpf.NewModuleFromFile("main.bpf.o") + if err != nil { + log.Fatal(err) + } + defer bpfModule.Close() + + outerHash, err := bpfModule.GetMap("outer_hash") + if err != nil { + log.Fatal(err) + } + + templateInnerMap, err := bpf.CreateMap(bpf.MapTypeHash, "template_inner_map", 4, 4, 420, nil) + if err != nil { + log.Fatal(err) + } + + // As the "outer_hash" map does not have an inner map prototype pre-allocated, + // an active map (from any origin) must be used as a template, by calling + // SetInnerMap() before the object is loaded, otherwise the BPF program will + // fail to load. The template map can be removed after the object is loaded. + err = outerHash.SetInnerMap(templateInnerMap.FileDescriptor()) + if err != nil { + log.Fatal(err) + } + + err = bpfModule.BPFLoadObject() + if err != nil { + log.Fatal(err) + } + + // + // "outer_hash" map of maps is now fully loaded and can be used. + // + + // Attempting to set inner map after the object is loaded will fail. + err = outerHash.SetInnerMap(templateInnerMap.FileDescriptor()) + if err == nil { + log.Fatal("should fail after object is loaded") + } + + // If not needed anymore, remove the "template_inner_map", + // freeing up resources. + err = syscall.Close(templateInnerMap.FileDescriptor()) + if err != nil { + log.Fatal(err) + } +} diff --git a/selftest/map-setinner/run.sh b/selftest/map-setinner/run.sh new file mode 120000 index 00000000..aee911b2 --- /dev/null +++ b/selftest/map-setinner/run.sh @@ -0,0 +1 @@ +../common/run.sh \ No newline at end of file