Repository: cilium/ebpf
Pull request #63 (2020-02-17 17:38:10)

Add New(Map|Program)FromID
Signed-off-by: Lehner Florian <dev@der-flo.net>

This PR add the functions to get the FD of a map or program by its ID.

florianl: Thanks for your feedback and I've addressed them.

Patch:
diff --git a/map.go b/map.go
index 2b6badc..dfad214 100644
--- a/map.go
+++ b/map.go
@@ -602,18 +602,7 @@ func unmarshalMap(buf []byte) (*Map, error) {
 	// Looking up an entry in a nested map or prog array returns an id,
 	// not an fd.
 	id := internal.NativeEndian.Uint32(buf)
-	fd, err := bpfGetMapFDByID(id)
-	if err != nil {
-		return nil, err
-	}
-
-	name, abi, err := newMapABIFromFd(fd)
-	if err != nil {
-		_ = fd.Close()
-		return nil, err
-	}
-
-	return newMap(fd, name, abi)
+	return NewMapFromID(MapID(id))
 }
 
 // MarshalBinary implements BinaryMarshaler.
@@ -776,3 +765,30 @@ func MapGetNextID(startID MapID) (MapID, error) {
 	id, err := objGetNextID(_MapGetNextID, uint32(startID))
 	return MapID(id), err
 }
+
+// NewMapFromID returns the map for a given id.
+//
+// Returns ErrNotExist, if there is no eBPF map with the given id.
+func NewMapFromID(id MapID) (*Map, error) {
+	fd, err := bpfObjGetFDByID(_MapGetFDByID, uint32(id))
+	if err != nil {
+		return nil, err
+	}
+
+	name, abi, err := newMapABIFromFd(fd)
+	if err != nil {
+		_ = fd.Close()
+		return nil, err
+	}
+
+	return newMap(fd, name, abi)
+}
+
+// ID returns the systemwide unique ID of the map.
+func (m *Map) ID() (MapID, error) {
+	info, err := bpfGetMapInfoByFD(m.fd)
+	if err != nil {
+		return MapID(0), err
+	}
+	return MapID(info.id), nil
+}
diff --git a/prog.go b/prog.go
index 9be13d9..6be318f 100644
--- a/prog.go
+++ b/prog.go
@@ -419,18 +419,7 @@ func unmarshalProgram(buf []byte) (*Program, error) {
 	// Looking up an entry in a nested map or prog array returns an id,
 	// not an fd.
 	id := internal.NativeEndian.Uint32(buf)
-	fd, err := bpfGetProgramFDByID(id)
-	if err != nil {
-		return nil, err
-	}
-
-	name, abi, err := newProgramABIFromFd(fd)
-	if err != nil {
-		_ = fd.Close()
-		return nil, err
-	}
-
-	return newProgram(fd, name, abi), nil
+	return NewProgramFromID(ProgramID(id))
 }
 
 // MarshalBinary implements BinaryMarshaler.
@@ -523,8 +512,35 @@ func SanitizeName(name string, replacement rune) string {
 
 // ProgramGetNextID returns the ID of the next eBPF program.
 //
-// // Returns ErrNotExist, if there is no next eBPF program.
+// Returns ErrNotExist, if there is no next eBPF program.
 func ProgramGetNextID(startID ProgramID) (ProgramID, error) {
 	id, err := objGetNextID(_ProgGetNextID, uint32(startID))
 	return ProgramID(id), err
 }
+
+// NewProgramFromID returns the program for a given id.
+//
+// Returns ErrNotExist, if there is no eBPF program with the given id.
+func NewProgramFromID(id ProgramID) (*Program, error) {
+	fd, err := bpfObjGetFDByID(_ProgGetFDByID, uint32(id))
+	if err != nil {
+		return nil, err
+	}
+
+	name, abi, err := newProgramABIFromFd(fd)
+	if err != nil {
+		_ = fd.Close()
+		return nil, err
+	}
+
+	return newProgram(fd, name, abi), nil
+}
+
+// ID returns the systemwide unique ID of the program.
+func (p *Program) ID() (ProgramID, error) {
+	info, err := bpfGetProgInfoByFD(p.fd)
+	if err != nil {
+		return ProgramID(0), err
+	}
+	return ProgramID(info.id), nil
+}
diff --git a/syscalls.go b/syscalls.go
index fb9c858..20de356 100644
--- a/syscalls.go
+++ b/syscalls.go
@@ -469,26 +469,10 @@ var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func()
 	return true
 })
 
-func bpfGetMapFDByID(id uint32) (*internal.FD, error) {
-	// available from 4.13
+func bpfObjGetFDByID(cmd int, id uint32) (*internal.FD, error) {
 	attr := bpfGetFDByIDAttr{
 		id: id,
 	}
-	ptr, err := internal.BPF(_MapGetFDByID, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
-	if err != nil {
-		return nil, xerrors.Errorf("can't get fd for map id %d: %w", id, err)
-	}
-	return internal.NewFD(uint32(ptr)), nil
-}
-
-func bpfGetProgramFDByID(id uint32) (*internal.FD, error) {
-	// available from 4.13
-	attr := bpfGetFDByIDAttr{
-		id: id,
-	}
-	ptr, err := internal.BPF(_ProgGetFDByID, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
-	if err != nil {
-		return nil, xerrors.Errorf("can't get fd for program id %d: %w", id, err)
-	}
-	return internal.NewFD(uint32(ptr)), nil
+	ptr, err := internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
+	return internal.NewFD(uint32(ptr)), wrapObjError(err)
 }


Test patch:
diff --git a/map_test.go b/map_test.go
index edaa800..d0b43be 100644
--- a/map_test.go
+++ b/map_test.go
@@ -721,6 +721,30 @@ func TestMapGetNextID(t *testing.T) {
 	}
 }
 
+func TestNewMapFromID(t *testing.T) {
+	testutils.SkipOnOldKernel(t, "4.13", "bpf_map_get_fd_by_id")
+
+	hash := createHash()
+	defer hash.Close()
+	var next MapID
+	var err error
+
+	next, err = hash.ID()
+	if err != nil {
+		t.Fatal("Could not get ID of map:", err)
+	}
+
+	if _, err = NewMapFromID(next); err != nil {
+		t.Fatalf("Can't get map for ID %d: %v", uint32(next), err)
+	}
+
+	// As there can be multiple maps, we use max(uint32) as MapID to trigger an expected error.
+	_, err = NewMapFromID(MapID(math.MaxUint32))
+	if !xerrors.Is(err, ErrNotExist) {
+		t.Fatal("Expected ErrNotExist, got:", err)
+	}
+}
+
 type benchValue struct {
 	ID      uint32
 	Val16   uint16
diff --git a/prog_test.go b/prog_test.go
index 4921a01..e88b732 100644
--- a/prog_test.go
+++ b/prog_test.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"fmt"
 	"io/ioutil"
+	"math"
 	"os"
 	"path/filepath"
 	"strings"
@@ -360,6 +361,39 @@ func TestProgramGetNextID(t *testing.T) {
 	}
 }
 
+func TestNewProgramFromID(t *testing.T) {
+	testutils.SkipOnOldKernel(t, "4.13", "bpf_prog_get_fd_by_id")
+
+	prog, err := NewProgram(&ProgramSpec{
+		Type: SkSKB,
+		Instructions: asm.Instructions{
+			asm.LoadImm(asm.R0, 0, asm.DWord),
+			asm.Return(),
+		},
+		License: "MIT",
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer prog.Close()
+	var next ProgramID
+
+	next, err = prog.ID()
+	if err != nil {
+		t.Fatal("Could not get ID of program:", err)
+	}
+
+	if _, err = NewProgramFromID(next); err != nil {
+		t.Fatalf("Can't get FD for program ID %d: %v", uint32(next), err)
+	}
+
+	// As there can be multiple programs, we use max(uint32) as ProgramID to trigger an expected error.
+	_, err = NewProgramFromID(ProgramID(math.MaxUint32))
+	if !xerrors.Is(err, ErrNotExist) {
+		t.Fatal("Expected ErrNotExist, got:", err)
+	}
+}
+
 func createProgramArray(t *testing.T) *Map {
 	t.Helper()