From de57e915ff34190f6633850097196473c7d4ebff Mon Sep 17 00:00:00 2001 From: Lehner Florian Date: Fri, 14 Feb 2020 22:17:44 +0100 Subject: [PATCH] Add *GetNextID Signed-off-by: Lehner Florian --- map.go | 11 +++++++++++ map_test.go | 31 +++++++++++++++++++++++++++++++ prog.go | 11 +++++++++++ prog_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ syscalls.go | 30 ++++++++++++++++++++++++++++++ types.go | 1 + 6 files changed, 125 insertions(+) diff --git a/map.go b/map.go index 279d8afbd..2b6badc3c 100644 --- a/map.go +++ b/map.go @@ -17,6 +17,9 @@ var ( ErrIterationAborted = xerrors.New("iteration aborted") ) +// MapID represents the unique ID of an eBPF map +type MapID uint32 + // MapSpec defines a Map. type MapSpec struct { // Name is passed to the kernel as a debug aid. Must only contain @@ -765,3 +768,11 @@ func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool { func (mi *MapIterator) Err() error { return mi.err } + +// MapGetNextID returns the ID of the next eBPF map. +// +// Returns ErrNotExist, if there is no next eBPF map. +func MapGetNextID(startID MapID) (MapID, error) { + id, err := objGetNextID(_MapGetNextID, uint32(startID)) + return MapID(id), err +} diff --git a/map_test.go b/map_test.go index 1e402ba54..edaa80024 100644 --- a/map_test.go +++ b/map_test.go @@ -690,6 +690,37 @@ func TestMapFreeze(t *testing.T) { } } +func TestMapGetNextID(t *testing.T) { + testutils.SkipOnOldKernel(t, "4.13", "bpf_map_get_next_id") + var next MapID + var err error + + hash := createHash() + defer hash.Close() + + if next, err = MapGetNextID(MapID(0)); err != nil { + t.Fatal("Can't get next ID:", err) + } + if next == MapID(0) { + t.Fatal("Expected next ID other than 0") + } + + // As there can be multiple eBPF maps, we loop over all of them and + // make sure, the IDs increase and the last call will return ErrNotExist + for { + last := next + if next, err = MapGetNextID(last); err != nil { + if !xerrors.Is(err, ErrNotExist) { + t.Fatal("Expected ErrNotExist, got:", err) + } + break + } + if next <= last { + t.Fatalf("Expected next ID (%d) to be higher than the last ID (%d)", next, last) + } + } +} + type benchValue struct { ID uint32 Val16 uint16 diff --git a/prog.go b/prog.go index 4e0c134c6..9be13d901 100644 --- a/prog.go +++ b/prog.go @@ -19,6 +19,9 @@ import ( // ErrNotSupported is returned whenever the kernel doesn't support a feature. var ErrNotSupported = internal.ErrNotSupported +// ProgramID represents the unique ID of an eBPF program +type ProgramID uint32 + const ( // Number of bytes to pad the output buffer for BPF_PROG_TEST_RUN. // This is currently the maximum of spare space allocated for SKB @@ -517,3 +520,11 @@ func SanitizeName(name string, replacement rune) string { return char }, name) } + +// ProgramGetNextID returns the ID of the 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 +} diff --git a/prog_test.go b/prog_test.go index 26ba43834..4921a01dc 100644 --- a/prog_test.go +++ b/prog_test.go @@ -12,6 +12,7 @@ import ( "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/testutils" + "golang.org/x/xerrors" ) func TestProgramRun(t *testing.T) { @@ -319,6 +320,46 @@ func TestHaveProgTestRun(t *testing.T) { testutils.CheckFeatureTest(t, haveProgTestRun) } +func TestProgramGetNextID(t *testing.T) { + testutils.SkipOnOldKernel(t, "4.13", "bpf_prog_get_next_id") + var next ProgramID + + 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() + + if next, err = ProgramGetNextID(ProgramID(0)); err != nil { + t.Fatal("Can't get next ID:", err) + } + if next == ProgramID(0) { + t.Fatal("Expected next ID other than 0") + } + + // As there can be multiple eBPF programs, we loop over all of them and + // make sure, the IDs increase and the last call will return ErrNotExist + for { + last := next + if next, err = ProgramGetNextID(last); err != nil { + if !xerrors.Is(err, ErrNotExist) { + t.Fatal("Expected ErrNotExist, got:", err) + } + break + } + if next <= last { + t.Fatalf("Expected next ID (%d) to be higher than the last ID (%d)", next, last) + } + } +} + func createProgramArray(t *testing.T) *Map { t.Helper() diff --git a/syscalls.go b/syscalls.go index 12ac863c1..fb9c8583f 100644 --- a/syscalls.go +++ b/syscalls.go @@ -11,6 +11,11 @@ import ( "golang.org/x/xerrors" ) +// Generic errors returned by BPF syscalls. +var ( + ErrNotExist = xerrors.New("requested object does not exit") +) + // bpfObjName is a null-terminated string made up of // 'A-Za-z0-9_' characters. type bpfObjName [unix.BPF_OBJ_NAME_LEN]byte @@ -150,6 +155,12 @@ type bpfMapFreezeAttr struct { mapFd uint32 } +type bpfObjGetNextIDAttr struct { + startID uint32 + nextID uint32 + openFlags uint32 +} + func bpfProgLoad(attr *bpfProgLoadAttr) (*internal.FD, error) { for { fd, err := internal.BPF(_ProgLoad, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) @@ -301,6 +312,25 @@ func bpfMapGetNextKey(m *internal.FD, key, nextKeyOut internal.Pointer) error { return wrapMapError(err) } +func objGetNextID(cmd int, start uint32) (uint32, error) { + attr := bpfObjGetNextIDAttr{ + startID: start, + } + _, err := internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) + return attr.nextID, wrapObjError(err) +} + +func wrapObjError(err error) error { + if err == nil { + return nil + } + if xerrors.Is(err, unix.ENOENT) { + return xerrors.Errorf("%w", ErrNotExist) + } + + return xerrors.New(err.Error()) +} + func wrapMapError(err error) error { if err == nil { return nil diff --git a/types.go b/types.go index 6a0228dc7..673809e8d 100644 --- a/types.go +++ b/types.go @@ -115,6 +115,7 @@ const ( _TaskFDQuery _MapLookupAndDeleteElem _MapFreeze + _BTFGetNextID ) const (