From 4c31e33e447a025ac0ad16ddd7a291ee67d402a6 Mon Sep 17 00:00:00 2001 From: Mahe Tardy Date: Fri, 20 Dec 2024 17:25:26 +0100 Subject: [PATCH] pkg/bugtool: fix walking bpf fs using new c/ebpf pin pkg PR cilium/ebpf#1626 added the new pin package that simplifies a lot how one can parse a bpf fs. I just love it. Signed-off-by: Mahe Tardy --- pkg/bugtool/maps.go | 73 +++++++---------------- vendor/github.com/cilium/ebpf/pin/doc.go | 3 + vendor/github.com/cilium/ebpf/pin/load.go | 40 +++++++++++++ vendor/github.com/cilium/ebpf/pin/walk.go | 49 +++++++++++++++ vendor/modules.txt | 1 + 5 files changed, 113 insertions(+), 53 deletions(-) create mode 100644 vendor/github.com/cilium/ebpf/pin/doc.go create mode 100644 vendor/github.com/cilium/ebpf/pin/load.go create mode 100644 vendor/github.com/cilium/ebpf/pin/walk.go diff --git a/pkg/bugtool/maps.go b/pkg/bugtool/maps.go index a836056ff23..f21477eebb9 100644 --- a/pkg/bugtool/maps.go +++ b/pkg/bugtool/maps.go @@ -10,13 +10,12 @@ import ( "iter" "maps" "os" - "path/filepath" "slices" "sort" - "strings" "syscall" "github.com/cilium/ebpf" + "github.com/cilium/ebpf/pin" "github.com/cilium/tetragon/pkg/bpf" ) @@ -90,27 +89,20 @@ func FindAllMaps() ([]bpf.ExtendedMapInfo, error) { // specified as argument. func FindPinnedMaps(path string) ([]bpf.ExtendedMapInfo, error) { var infos []bpf.ExtendedMapInfo - err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error { + + err := pin.WalkDir(path, func(path string, d fs.DirEntry, obj pin.Pinner, err error) error { if err != nil { return err } if d.IsDir() { return nil // skip directories } - m, err := ebpf.LoadPinnedMap(path, nil) - if err != nil { - return fmt.Errorf("failed to load pinned map %q: %w", path, err) - } - defer m.Close() - // check if it's really a map because ebpf.LoadPinnedMap does not return - // an error but garbage info on doing this on a prog - if ok, err := isMap(m.FD()); err != nil || !ok { - if err != nil { - return err - } + m, ok := obj.(*ebpf.Map) + if !ok { return nil // skip non map } + defer m.Close() xInfo, err := bpf.ExtendedInfoFromMap(m) if err != nil { @@ -153,58 +145,33 @@ func mapIDsFromProgs(prog *ebpf.Program) (iter.Seq[int], error) { func mapIDsFromPinnedProgs(path string) (iter.Seq[int], error) { mapSet := map[int]bool{} progArrays := []*ebpf.Map{} - err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error { + err := pin.WalkDir(path, func(path string, d fs.DirEntry, obj pin.Pinner, err error) error { if err != nil { return err } if d.IsDir() { return nil // skip directories } - if strings.HasSuffix(path, "/link") || strings.HasSuffix(path, "/link_override") { - return nil // skip BPF links, they make the syscall fail since cilium/ebpf@78074c59 - } - prog, err := ebpf.LoadPinnedProgram(path, nil) - if err != nil { - return fmt.Errorf("failed to load pinned object %q: %w", path, err) - } - defer prog.Close() - if ok, err := isProg(prog.FD()); err != nil || !ok { + switch typedObj := obj.(type) { + case *ebpf.Program: + newIDs, err := mapIDsFromProgs(typedObj) if err != nil { - return err + return fmt.Errorf("failed to retrieve map IDs from prog: %w", err) } - - // we want to keep a ref to prog array containing tail calls to - // search reference to map inside - ok, err := isMap(prog.FD()) - if err != nil { - return err + typedObj.Close() + for id := range newIDs { + mapSet[id] = true } - if ok { - m, err := ebpf.LoadPinnedMap(path, &ebpf.LoadPinOptions{ - ReadOnly: true, - }) - if err != nil { - return fmt.Errorf("failed to load pinned map %q: %w", path, err) - } - if m.Type() == ebpf.ProgramArray { - progArrays = append(progArrays, m) - // don't forget to close those files when used later on - } else { - m.Close() - } + case *ebpf.Map: + if typedObj.Type() == ebpf.ProgramArray { + progArrays = append(progArrays, typedObj) + // don't forget to close those files when used later on + } else { + typedObj.Close() } - - return nil // skip the non-prog } - newIDs, err := mapIDsFromProgs(prog) - if err != nil { - return fmt.Errorf("failed to retrieve map IDs from prog: %w", err) - } - for id := range newIDs { - mapSet[id] = true - } return nil }) if err != nil { diff --git a/vendor/github.com/cilium/ebpf/pin/doc.go b/vendor/github.com/cilium/ebpf/pin/doc.go new file mode 100644 index 00000000000..3b602906887 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/pin/doc.go @@ -0,0 +1,3 @@ +// Package pin provides utility functions for working with pinned objects on bpffs. + +package pin diff --git a/vendor/github.com/cilium/ebpf/pin/load.go b/vendor/github.com/cilium/ebpf/pin/load.go new file mode 100644 index 00000000000..817e177e2b1 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/pin/load.go @@ -0,0 +1,40 @@ +package pin + +import ( + "fmt" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal/sys" + "github.com/cilium/ebpf/link" +) + +// Pinner is an interface implemented by all eBPF objects that support pinning +// to a bpf virtual filesystem. +type Pinner interface { + Pin(string) error +} + +// Load retrieves a pinned object from a bpf virtual filesystem. It returns one +// of [ebpf.Map], [ebpf.Program], or [link.Link]. +// +// Trying to open anything other than a bpf object is an error. +func Load(path string, opts *ebpf.LoadPinOptions) (Pinner, error) { + fd, typ, err := sys.ObjGetTyped(&sys.ObjGetAttr{ + Pathname: sys.NewStringPointer(path), + FileFlags: opts.Marshal(), + }) + if err != nil { + return nil, fmt.Errorf("opening pin %s: %w", path, err) + } + + switch typ { + case sys.BPF_TYPE_MAP: + return ebpf.NewMapFromFD(fd.Disown()) + case sys.BPF_TYPE_PROG: + return ebpf.NewProgramFromFD(fd.Disown()) + case sys.BPF_TYPE_LINK: + return link.NewFromFD(fd.Disown()) + } + + return nil, fmt.Errorf("unknown object type %d", typ) +} diff --git a/vendor/github.com/cilium/ebpf/pin/walk.go b/vendor/github.com/cilium/ebpf/pin/walk.go new file mode 100644 index 00000000000..96335f0021a --- /dev/null +++ b/vendor/github.com/cilium/ebpf/pin/walk.go @@ -0,0 +1,49 @@ +package pin + +import ( + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/cilium/ebpf/internal/linux" + "github.com/cilium/ebpf/internal/unix" +) + +// WalkDirFunc is the type of the function called for each object visited by +// [WalkDir]. It's identical to [fs.WalkDirFunc], but with an extra [Pinner] +// argument. If the visited node is a directory, obj is nil. +// +// err contains any errors encountered during bpffs traversal or object loading. +type WalkDirFunc func(path string, d fs.DirEntry, obj Pinner, err error) error + +// WalkDir walks the file tree rooted at path, calling bpffn for each node in +// the tree, including directories. Running WalkDir on a non-bpf filesystem is +// an error. Otherwise identical in behavior to [fs.WalkDir]. +// +// See the [WalkDirFunc] for more information. +func WalkDir(root string, bpffn WalkDirFunc) error { + fsType, err := linux.FSType(root) + if err != nil { + return err + } + if fsType != unix.BPF_FS_MAGIC { + return fmt.Errorf("%s is not on a bpf filesystem", root) + } + + fn := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return bpffn(path, nil, nil, err) + } + + if d.IsDir() { + return bpffn(path, d, nil, err) + } + + obj, err := Load(filepath.Join(root, path), nil) + + return bpffn(path, d, obj, err) + } + + return fs.WalkDir(os.DirFS(root), ".", fn) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 11ca3dae54c..e5c1ae810ab 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -81,6 +81,7 @@ github.com/cilium/ebpf/internal/tracefs github.com/cilium/ebpf/internal/unix github.com/cilium/ebpf/link github.com/cilium/ebpf/perf +github.com/cilium/ebpf/pin github.com/cilium/ebpf/rlimit # github.com/cilium/little-vm-helper v0.0.19 ## explicit; go 1.22.0