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

variable: return native Go pointers to bpf map memory #1607

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ type CollectionOptions struct {
// The given Maps are Clone()d before being used in the Collection, so the
// caller can Close() them freely when they are no longer needed.
MapReplacements map[string]*Map

// UnsafeVariableExperiment enables the unsafe variable experiment, allowing
// the use of [VariablePointer] for direct memory access and atomic operations
// on global bpf variables.
//
// Experimental: enabling this may cause segfaults or compromise the memory
// integrity of your Go application or the bpf maps representing Variables.
// Use at your own risk.
UnsafeVariableExperiment bool
}

// CollectionSpec describes a collection.
Expand Down Expand Up @@ -618,7 +627,12 @@ func (cl *collectionLoader) loadVariable(varName string) (*Variable, error) {
// emit a Variable with a nil Memory. This keeps Collection{Spec}.Variables
// consistent across systems with different feature sets without breaking
// LoadAndAssign.
mm, err := m.Memory()
var mm *Memory
if cl.opts.UnsafeVariableExperiment {
mm, err = m.unsafeMemory()
} else {
mm, err = m.Memory()
}
if err != nil && !errors.Is(err, ErrNotSupported) {
return nil, fmt.Errorf("variable %s: getting memory for map %s: %w", varName, mapName, err)
}
Expand Down
6 changes: 6 additions & 0 deletions internal/unix/types_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package unix

import (
"syscall"
"unsafe"

linux "golang.org/x/sys/unix"
)
Expand Down Expand Up @@ -38,6 +39,7 @@ const (
PROT_WRITE = linux.PROT_WRITE
MAP_ANON = linux.MAP_ANON
MAP_SHARED = linux.MAP_SHARED
MAP_FIXED = linux.MAP_FIXED
MAP_PRIVATE = linux.MAP_PRIVATE
PERF_ATTR_SIZE_VER1 = linux.PERF_ATTR_SIZE_VER1
PERF_TYPE_SOFTWARE = linux.PERF_TYPE_SOFTWARE
Expand Down Expand Up @@ -136,6 +138,10 @@ func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, e
return linux.Mmap(fd, offset, length, prot, flags)
}

func MmapPtr(fd int, offset int64, addr unsafe.Pointer, length uintptr, prot int, flags int) (ret unsafe.Pointer, err error) {
return linux.MmapPtr(fd, offset, addr, length, prot, flags)
}

func Munmap(b []byte) (err error) {
return linux.Munmap(b)
}
Expand Down
6 changes: 6 additions & 0 deletions internal/unix/types_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package unix

import (
"syscall"
"unsafe"
)

// Constants are distinct to avoid breaking switch statements.
Expand Down Expand Up @@ -37,6 +38,7 @@ const (
PROT_WRITE
MAP_ANON
MAP_SHARED
MAP_FIXED
MAP_PRIVATE
PERF_ATTR_SIZE_VER1
PERF_TYPE_SOFTWARE
Expand Down Expand Up @@ -197,6 +199,10 @@ func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, e
return []byte{}, errNonLinux()
}

func MmapPtr(fd int, offset int64, addr unsafe.Pointer, length uintptr, prot int, flags int) (ret unsafe.Pointer, err error) {
return nil, errNonLinux()
}

func Munmap(b []byte) (err error) {
return errNonLinux()
}
Expand Down
28 changes: 28 additions & 0 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,34 @@ func (m *Map) Memory() (*Memory, error) {
return mm, nil
}

// unsafeMemory returns a heap-mapped memory region for the Map. The Map must
// have been created with the BPF_F_MMAPABLE flag. Repeated calls to Memory
// return the same mapping. Callers are responsible for coordinating access to
// Memory.
func (m *Map) unsafeMemory() (*Memory, error) {
if m.memory != nil {
return m.memory, nil
}

if m.flags&sys.BPF_F_MMAPABLE == 0 {
return nil, fmt.Errorf("Map was not created with the BPF_F_MMAPABLE flag: %w", ErrNotSupported)
}

size, err := m.memorySize()
if err != nil {
return nil, err
}

mm, err := newUnsafeMemory(m.FD(), size)
if err != nil {
return nil, fmt.Errorf("creating new Memory: %w", err)
}

m.memory = mm

return mm, nil
}

func (m *Map) memorySize() (int, error) {
switch m.Type() {
case Array:
Expand Down
6 changes: 4 additions & 2 deletions memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ var ErrReadOnly = errors.New("resource is read-only")
// for individual values. For accesses beyond a single value, the usual
// concurrent programming rules apply.
type Memory struct {
b []byte
ro bool
b []byte
ro bool
heap bool
}

func newMemory(fd, size int) (*Memory, error) {
Expand Down Expand Up @@ -64,6 +65,7 @@ func newMemory(fd, size int) (*Memory, error) {
mm := &Memory{
b,
ro,
false,
}
runtime.SetFinalizer(mm, (*Memory).close)

Expand Down
2 changes: 1 addition & 1 deletion memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func TestMemoryReadOnly(t *testing.T) {
qt.Assert(t, qt.IsTrue(fz.ReadOnly()))
}

func TestMemoryUnmap(t *testing.T) {
func TestMemoryClose(t *testing.T) {
mm, err := mustMmapableArray(t, 0).Memory()
qt.Assert(t, qt.IsNil(err))

Expand Down
Loading