From 70d093bf85ef01ae14271b264b97fc5559391fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geyslan=20Greg=C3=B3rio?= Date: Mon, 7 Aug 2023 17:38:03 -0300 Subject: [PATCH] feat(map): introduce ReuseFD() to reuse a map fd The `ReuseFD` function links the current BPFMap instance to a provided map file descriptor. This enables the reuse of a map that was initiated by another process. Initial effort: #346 Co-authored-by: Francisco Javier Honduvilla Coto --- map-low.go | 29 ++++++++++ map.go | 30 +++++++++-- selftest/reuse-fd/Makefile | 1 + selftest/reuse-fd/go.mod | 7 +++ selftest/reuse-fd/go.sum | 4 ++ selftest/reuse-fd/main.bpf.c | 23 ++++++++ selftest/reuse-fd/main.bpf.h | 6 +++ selftest/reuse-fd/main.go | 102 +++++++++++++++++++++++++++++++++++ selftest/reuse-fd/run.sh | 1 + 9 files changed, 200 insertions(+), 3 deletions(-) create mode 120000 selftest/reuse-fd/Makefile create mode 100644 selftest/reuse-fd/go.mod create mode 100644 selftest/reuse-fd/go.sum create mode 100644 selftest/reuse-fd/main.bpf.c create mode 100644 selftest/reuse-fd/main.bpf.h create mode 100644 selftest/reuse-fd/main.go create mode 120000 selftest/reuse-fd/run.sh diff --git a/map-low.go b/map-low.go index 73075328..2e7e69a1 100644 --- a/map-low.go +++ b/map-low.go @@ -89,6 +89,35 @@ func (m *BPFMapLow) FileDescriptor() int { return m.fd } +func (m *BPFMapLow) ReuseFD(fd int) error { + info, err := GetMapInfoByFD(fd) + if err != nil { + return fmt.Errorf("failed to reuse fd %d: %w", fd, err) + } + + newFD, err := syscall.Open("/", syscall.O_RDONLY|syscall.O_CLOEXEC, 0) + if err != nil { + return fmt.Errorf("failed to reuse fd %d: %w", fd, err) + } + + err = syscall.Dup3(fd, newFD, syscall.O_CLOEXEC) + if err != nil { + _ = syscall.Close(newFD) + return fmt.Errorf("failed to reuse fd %d: %w", fd, err) + } + + err = syscall.Close(m.FileDescriptor()) + if err != nil { + _ = syscall.Close(newFD) + return fmt.Errorf("failed to reuse fd %d: %w", fd, err) + } + + m.fd = newFD + m.info = info + + return nil +} + func (m *BPFMapLow) Name() string { return m.info.Name } diff --git a/map.go b/map.go index dccc7a67..8266665a 100644 --- a/map.go +++ b/map.go @@ -45,9 +45,33 @@ func (m *BPFMap) GetFd() int { return m.FileDescriptor() } -// bpf_map__reuse_fd -// func (m *BPFMap) ReuseFD(fd int) error { -// } +// ReuseFD associates the BPFMap instance with the provided map file descriptor. +// +// This function is useful for reusing a map that was previously created by a +// different process. By passing the file descriptor of the existing map, the +// current BPFMap instance becomes linked to that map. +// +// NOTE: The function closes the current file descriptor associated with the +// BPFMap instance and replaces it with a duplicated descriptor pointing to the +// given fd. As a result, the instance original file descriptor becomes invalid, +// and all associated information is overwritten. +func (m *BPFMap) ReuseFD(fd int) error { + retC := C.bpf_map__reuse_fd(m.bpfMap, C.int(fd)) + if retC < 0 { + return fmt.Errorf("failed to reuse fd %d: %w", fd, syscall.Errno(-retC)) + } + + newFD := m.FileDescriptor() + info, err := GetMapInfoByFD(newFD) + if err != nil { + return err + } + + m.bpfMapLow.fd = newFD + m.bpfMapLow.info = info + + return nil +} func (m *BPFMap) Name() string { return C.GoString(C.bpf_map__name(m.bpfMap)) diff --git a/selftest/reuse-fd/Makefile b/selftest/reuse-fd/Makefile new file mode 120000 index 00000000..d981720c --- /dev/null +++ b/selftest/reuse-fd/Makefile @@ -0,0 +1 @@ +../common/Makefile \ No newline at end of file diff --git a/selftest/reuse-fd/go.mod b/selftest/reuse-fd/go.mod new file mode 100644 index 00000000..1fd0933d --- /dev/null +++ b/selftest/reuse-fd/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/reuse-fd/go.sum b/selftest/reuse-fd/go.sum new file mode 100644 index 00000000..c60af667 --- /dev/null +++ b/selftest/reuse-fd/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/reuse-fd/main.bpf.c b/selftest/reuse-fd/main.bpf.c new file mode 100644 index 00000000..d6498a43 --- /dev/null +++ b/selftest/reuse-fd/main.bpf.c @@ -0,0 +1,23 @@ +//+build ignore + +#include + +#include + +#include "main.bpf.h" + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, u32); + __type(value, struct value); + __uint(max_entries, 1 << 4); +} tester SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, u32); + __type(value, struct value); + __uint(max_entries, 1 << 4); +} tester_reused SEC(".maps"); + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/selftest/reuse-fd/main.bpf.h b/selftest/reuse-fd/main.bpf.h new file mode 100644 index 00000000..e4669be2 --- /dev/null +++ b/selftest/reuse-fd/main.bpf.h @@ -0,0 +1,6 @@ +struct value { + char a; + char b; + char c; + char d; +}; \ No newline at end of file diff --git a/selftest/reuse-fd/main.go b/selftest/reuse-fd/main.go new file mode 100644 index 00000000..e709b686 --- /dev/null +++ b/selftest/reuse-fd/main.go @@ -0,0 +1,102 @@ +package main + +// #include "main.bpf.h" +import "C" + +import ( + "bytes" + "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) + } + + testerReusedMap, err := bpfModule.GetMap("tester_reused") + if err != nil { + log.Fatal(err) + } + + // + // BPFMap ReuseFD + // + + // The current instance of "tester_reused" will be closed, and testerReusedMap + // will now point to the same map as testerMap "tester", however, via a + // different FD. + err = testerReusedMap.ReuseFD(testerMap.FileDescriptor()) + if err != nil { + log.Fatal(err) + } + + if err != nil { + log.Fatal(err) + } + + valueSize := C.sizeof_struct_value + + key1 := uint32(1) + value1 := make([]byte, valueSize) + value1[0] = '7' + value1[1] = '1' + value1[2] = '9' + value1[3] = '1' + key1Unsafe := unsafe.Pointer(&key1) + value1Unsafe := unsafe.Pointer(&value1[0]) + err = testerMap.Update(key1Unsafe, value1Unsafe) // update "tester" + if err != nil { + log.Fatal(err) + } + + key2 := int32(42069420) + value2 := make([]byte, valueSize) + value2[0] = '1' + value2[1] = '1' + value2[2] = '0' + value2[3] = '7' + key2Unsafe := unsafe.Pointer(&key2) + value2Unsafe := unsafe.Pointer(&value2[0]) + err = testerReusedMap.Update(key2Unsafe, value2Unsafe) // also update "tester" + if err != nil { + log.Fatal(err) + } + + // + // BPFMapLow ReuseFD + // + + toReuseCreated, err := bpf.CreateMap(bpf.MapTypeArray, "toreuse", 4, 4, 420, nil) + if err != nil { + log.Fatal(err) + } + + // The current instance of "toreuse" will be closed, and toReuseCreated + // will now point to the same map as testerMap "tester", however, via a + // different FD. + err = toReuseCreated.ReuseFD(testerMap.FileDescriptor()) + if err != nil { + log.Fatal(err) + } + + val2, err := toReuseCreated.GetValue(key2Unsafe) // lookup "tester" + if err != nil { + log.Fatal(err) + } + + if !bytes.Equal(val2, value2) { + log.Fatal("wrong value") + } +} diff --git a/selftest/reuse-fd/run.sh b/selftest/reuse-fd/run.sh new file mode 120000 index 00000000..aee911b2 --- /dev/null +++ b/selftest/reuse-fd/run.sh @@ -0,0 +1 @@ +../common/run.sh \ No newline at end of file