Skip to content

Commit 0a77f25

Browse files
committed
pin: new package for loading bpf pins and walking bpffs directories
This commit adds a new package pin with two main APIs: Load() and WalkDir(). It's split off from the root package since ebpf cannot import link. Signed-off-by: Timo Beckers <[email protected]>
1 parent 981518b commit 0a77f25

File tree

5 files changed

+231
-0
lines changed

5 files changed

+231
-0
lines changed

pin/doc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Package pin provides utility functions for working with pinned objects on bpffs.
2+
3+
package pin

pin/load.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package pin
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/cilium/ebpf"
7+
"github.com/cilium/ebpf/internal/sys"
8+
"github.com/cilium/ebpf/link"
9+
)
10+
11+
// Pinner is an interface implemented by all eBPF objects that support pinning
12+
// to a bpf virtual filesystem.
13+
type Pinner interface {
14+
Pin(string) error
15+
}
16+
17+
// Load retrieves a pinned object from a bpf virtual filesystem. It returns one
18+
// of [ebpf.Map], [ebpf.Program], or [link.Link].
19+
//
20+
// Trying to open anything other than a bpf object is an error.
21+
func Load(path string, opts *ebpf.LoadPinOptions) (Pinner, error) {
22+
fd, typ, err := sys.ObjGetTyped(&sys.ObjGetAttr{
23+
Pathname: sys.NewStringPointer(path),
24+
FileFlags: opts.Marshal(),
25+
})
26+
if err != nil {
27+
return nil, fmt.Errorf("opening pin %s: %w", path, err)
28+
}
29+
30+
switch typ {
31+
case sys.BPF_TYPE_MAP:
32+
return ebpf.NewMapFromFD(fd.Disown())
33+
case sys.BPF_TYPE_PROG:
34+
return ebpf.NewProgramFromFD(fd.Disown())
35+
case sys.BPF_TYPE_LINK:
36+
return link.NewFromFD(fd.Disown())
37+
}
38+
39+
return nil, fmt.Errorf("unknown object type %d", typ)
40+
}

pin/load_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package pin
2+
3+
import (
4+
"path/filepath"
5+
"testing"
6+
7+
"github.com/go-quicktest/qt"
8+
9+
"github.com/cilium/ebpf"
10+
"github.com/cilium/ebpf/asm"
11+
"github.com/cilium/ebpf/internal/testutils"
12+
)
13+
14+
func mustPinnedProgram(t *testing.T, path string) *ebpf.Program {
15+
t.Helper()
16+
17+
spec := &ebpf.ProgramSpec{
18+
Name: "test",
19+
Type: ebpf.SocketFilter,
20+
Instructions: asm.Instructions{
21+
asm.LoadImm(asm.R0, 2, asm.DWord),
22+
asm.Return(),
23+
},
24+
License: "MIT",
25+
}
26+
27+
p, err := ebpf.NewProgram(spec)
28+
if err != nil {
29+
t.Fatal(err)
30+
}
31+
t.Cleanup(func() { p.Close() })
32+
33+
if err := p.Pin(path); err != nil {
34+
t.Fatal(err)
35+
}
36+
37+
return p
38+
}
39+
40+
func mustPinnedMap(t *testing.T, path string) *ebpf.Map {
41+
t.Helper()
42+
43+
spec := &ebpf.MapSpec{
44+
Name: "test",
45+
Type: ebpf.Array,
46+
KeySize: 4,
47+
ValueSize: 4,
48+
MaxEntries: 1,
49+
}
50+
51+
m, err := ebpf.NewMap(spec)
52+
if err != nil {
53+
t.Fatal(err)
54+
}
55+
t.Cleanup(func() { m.Close() })
56+
57+
if err := m.Pin(path); err != nil {
58+
t.Fatal(err)
59+
}
60+
61+
return m
62+
}
63+
64+
func TestLoad(t *testing.T) {
65+
testutils.SkipOnOldKernel(t, "4.10", "reading program fdinfo")
66+
67+
tmp := testutils.TempBPFFS(t)
68+
69+
mpath := filepath.Join(tmp, "map")
70+
ppath := filepath.Join(tmp, "prog")
71+
72+
mustPinnedMap(t, mpath)
73+
mustPinnedProgram(t, ppath)
74+
75+
_, err := Load(tmp, nil)
76+
qt.Assert(t, qt.IsNotNil(err))
77+
78+
m, err := Load(mpath, nil)
79+
qt.Assert(t, qt.IsNil(err))
80+
qt.Assert(t, qt.Satisfies(m, testutils.Contains[*ebpf.Map]))
81+
82+
p, err := Load(ppath, nil)
83+
qt.Assert(t, qt.IsNil(err))
84+
qt.Assert(t, qt.Satisfies(p, testutils.Contains[*ebpf.Program]))
85+
}

pin/walk.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package pin
2+
3+
import (
4+
"fmt"
5+
"io/fs"
6+
"os"
7+
"path/filepath"
8+
9+
"github.com/cilium/ebpf/internal/linux"
10+
"github.com/cilium/ebpf/internal/unix"
11+
)
12+
13+
// WalkDirFunc is the type of the function called for each object visited by
14+
// [WalkDir]. It's identical to [fs.WalkDirFunc], but with an extra [Pinner]
15+
// argument. If the visited node is a directory, obj is nil.
16+
//
17+
// err contains any errors encountered during bpffs traversal or object loading.
18+
type WalkDirFunc func(path string, d fs.DirEntry, obj Pinner, err error) error
19+
20+
// WalkDir walks the file tree rooted at path, calling bpffn for each node in
21+
// the tree, including directories. Running WalkDir on a non-bpf filesystem is
22+
// an error. Otherwise identical in behavior to [fs.WalkDir].
23+
//
24+
// See the [WalkDirFunc] for more information.
25+
func WalkDir(root string, bpffn WalkDirFunc) error {
26+
fsType, err := linux.FSType(root)
27+
if err != nil {
28+
return err
29+
}
30+
if fsType != unix.BPF_FS_MAGIC {
31+
return fmt.Errorf("%s is not on a bpf filesystem", root)
32+
}
33+
34+
fn := func(path string, d fs.DirEntry, err error) error {
35+
if err != nil {
36+
return bpffn(path, nil, nil, err)
37+
}
38+
39+
if d.IsDir() {
40+
return bpffn(path, d, nil, err)
41+
}
42+
43+
obj, err := Load(filepath.Join(root, path), nil)
44+
45+
return bpffn(path, d, obj, err)
46+
}
47+
48+
return fs.WalkDir(os.DirFS(root), ".", fn)
49+
}

pin/walk_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package pin
2+
3+
import (
4+
"io/fs"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/go-quicktest/qt"
10+
11+
"github.com/cilium/ebpf"
12+
"github.com/cilium/ebpf/internal/testutils"
13+
)
14+
15+
func TestWalkDir(t *testing.T) {
16+
testutils.SkipOnOldKernel(t, "4.10", "reading program fdinfo")
17+
18+
tmp := testutils.TempBPFFS(t)
19+
dir := filepath.Join(tmp, "dir")
20+
qt.Assert(t, qt.IsNil(os.Mkdir(dir, 0755)))
21+
22+
mustPinnedProgram(t, filepath.Join(tmp, "pinned_prog"))
23+
mustPinnedMap(t, filepath.Join(dir, "pinned_map"))
24+
25+
entries := make(map[string]string)
26+
27+
bpffn := func(path string, d fs.DirEntry, obj Pinner, err error) error {
28+
qt.Assert(t, qt.IsNil(err))
29+
30+
if path == "." {
31+
return nil
32+
}
33+
34+
switch obj.(type) {
35+
case *ebpf.Program:
36+
entries[path] = "prog"
37+
case *ebpf.Map:
38+
entries[path] = "map"
39+
default:
40+
entries[path] = ""
41+
}
42+
43+
return nil
44+
}
45+
qt.Assert(t, qt.IsNil(WalkDir(tmp, bpffn)))
46+
47+
qt.Assert(t, qt.DeepEquals(entries, map[string]string{
48+
"pinned_prog": "prog",
49+
"dir": "",
50+
"dir/pinned_map": "map",
51+
}))
52+
53+
qt.Assert(t, qt.IsNotNil(WalkDir("/", nil)))
54+
}

0 commit comments

Comments
 (0)