Skip to content
Closed
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
81 changes: 81 additions & 0 deletions bpffs/bpffs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package bpffs

import (
"errors"
"fmt"
"path/filepath"
"syscall"

"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)

const bpffsMountPath = "/sys/fs/bpf"

type BPFFS struct {
path string
bpffsFd *sys.FD
tokenFd *sys.FD
}

func NewBPFFSFromPath(path string) (*BPFFS, error) {
if path == "" {
path = bpffsMountPath
} else {
path = filepath.Clean(path)
}

fd, err := unix.Open(path, syscall.O_DIRECTORY|syscall.O_RDONLY, 0)
if err != nil {
return nil, err
}

bpffsFd, err := sys.NewFD(fd)
if err != nil {
return nil, err
}

return &BPFFS{
path: path,
bpffsFd: bpffsFd,
}, nil
}

func (bf *BPFFS) Close() error {
var errs []error

if bf.bpffsFd != nil {
if err := bf.bpffsFd.Close(); err != nil {
errs = append(errs, err)
}
}
if bf.tokenFd != nil {
if err := bf.tokenFd.Close(); err != nil {
errs = append(errs, err)
}
}

return errors.Join(errs...)
}

func (bf *BPFFS) Token() (*sys.FD, error) {
if bf.tokenFd != nil {
return bf.tokenFd.Dup()
}

if bf.bpffsFd == nil {
return nil, fmt.Errorf("BPFFS is not mounted")
}

tokenAttr := sys.TokenCreateAttr{
BpffsFd: bf.bpffsFd.Uint(),
}

tokenFd, err := sys.TokenCreate(&tokenAttr)
if err != nil {
return nil, err
}

bf.tokenFd = tokenFd
return bf.tokenFd.Dup()
}
4 changes: 3 additions & 1 deletion btf/btf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"github.com/cilium/ebpf/internal/testutils"
)

const NotBPFTokenFd int32 = -1

func vmlinuxSpec(tb testing.TB) *Spec {
tb.Helper()

Expand Down Expand Up @@ -306,7 +308,7 @@ func TestLoadSpecFromElf(t *testing.T) {
func TestVerifierError(t *testing.T) {
b, err := NewBuilder([]Type{&Int{Encoding: 255}})
qt.Assert(t, qt.IsNil(err))
_, err = NewHandle(b)
_, err = NewHandle(b, NotBPFTokenFd)
testutils.SkipIfNotSupported(t, err)
var ve *internal.VerifierError
if !errors.As(err, &ve) {
Expand Down
245 changes: 134 additions & 111 deletions btf/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,130 +11,147 @@ import (

// haveBTF attempts to load a BTF blob containing an Int. It should pass on any
// kernel that supports BPF_BTF_LOAD.
var haveBTF = internal.NewFeatureTest("BTF", func() error {
// 0-length anonymous integer
err := probeBTF(&Int{})
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
return internal.ErrNotSupported
}
return err
}, "4.18")
var haveBTF = internal.NewFeatureTest("BTF",
func(opts ...internal.FeatureTestOption) error {
// 0-length anonymous integer
o := internal.BuildOptions(opts...)

err := probeBTF(&Int{}, o.BpffsTokenFd)
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
return internal.ErrNotSupported
}
return err
},
"4.18",
)

// haveMapBTF attempts to load a minimal BTF blob containing a Var. It is
// used as a proxy for .bss, .data and .rodata map support, which generally
// come with a Var and Datasec. These were introduced in Linux 5.2.
var haveMapBTF = internal.NewFeatureTest("Map BTF (Var/Datasec)", func() error {
if err := haveBTF(); err != nil {
var haveMapBTF = internal.NewFeatureTest("Map BTF (Var/Datasec)",
func(opts ...internal.FeatureTestOption) error {
if err := haveBTF(opts...); err != nil {
return err
}

v := &Var{
Name: "a",
Type: &Pointer{(*Void)(nil)},
}

o := internal.BuildOptions(opts...)
err := probeBTF(v, o.BpffsTokenFd)
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
// Treat both EINVAL and EPERM as not supported: creating the map may still
// succeed without Btf* attrs.
return internal.ErrNotSupported
}
return err
}

v := &Var{
Name: "a",
Type: &Pointer{(*Void)(nil)},
}

err := probeBTF(v)
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
// Treat both EINVAL and EPERM as not supported: creating the map may still
// succeed without Btf* attrs.
return internal.ErrNotSupported
}
return err
}, "5.2")
}, "5.2")

// haveProgBTF attempts to load a BTF blob containing a Func and FuncProto. It
// is used as a proxy for ext_info (func_info) support, which depends on
// Func(Proto) by definition.
var haveProgBTF = internal.NewFeatureTest("Program BTF (func/line_info)", func() error {
if err := haveBTF(); err != nil {
var haveProgBTF = internal.NewFeatureTest("Program BTF (func/line_info)",
func(opts ...internal.FeatureTestOption) error {
if err := haveBTF(opts...); err != nil {
return err
}

fn := &Func{
Name: "a",
Type: &FuncProto{Return: (*Void)(nil)},
}

o := internal.BuildOptions(opts...)
err := probeBTF(fn, o.BpffsTokenFd)
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
return internal.ErrNotSupported
}
return err
}

fn := &Func{
Name: "a",
Type: &FuncProto{Return: (*Void)(nil)},
}

err := probeBTF(fn)
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
return internal.ErrNotSupported
}
return err
}, "5.0")

var haveFuncLinkage = internal.NewFeatureTest("BTF func linkage", func() error {
if err := haveProgBTF(); err != nil {
}, "5.0")

var haveFuncLinkage = internal.NewFeatureTest("BTF func linkage",
func(opts ...internal.FeatureTestOption) error {
if err := haveProgBTF(opts...); err != nil {
return err
}

fn := &Func{
Name: "a",
Type: &FuncProto{Return: (*Void)(nil)},
Linkage: GlobalFunc,
}

o := internal.BuildOptions(opts...)
err := probeBTF(fn, o.BpffsTokenFd)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
}

fn := &Func{
Name: "a",
Type: &FuncProto{Return: (*Void)(nil)},
Linkage: GlobalFunc,
}

err := probeBTF(fn)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
}, "5.6")

var haveDeclTags = internal.NewFeatureTest("BTF decl tags", func() error {
if err := haveBTF(); err != nil {
}, "5.6")

var haveDeclTags = internal.NewFeatureTest("BTF decl tags",
func(opts ...internal.FeatureTestOption) error {
if err := haveBTF(opts...); err != nil {
return err
}

t := &Typedef{
Name: "a",
Type: &Int{},
Tags: []string{"a"},
}

o := internal.BuildOptions(opts...)
err := probeBTF(t, o.BpffsTokenFd)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
}

t := &Typedef{
Name: "a",
Type: &Int{},
Tags: []string{"a"},
}

err := probeBTF(t)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
}, "5.16")

var haveTypeTags = internal.NewFeatureTest("BTF type tags", func() error {
if err := haveBTF(); err != nil {
}, "5.16")

var haveTypeTags = internal.NewFeatureTest("BTF type tags",
func(opts ...internal.FeatureTestOption) error {
if err := haveBTF(opts...); err != nil {
return err
}

t := &TypeTag{
Type: &Int{},
Value: "a",
}

o := internal.BuildOptions(opts...)
err := probeBTF(t, o.BpffsTokenFd)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
}

t := &TypeTag{
Type: &Int{},
Value: "a",
}

err := probeBTF(t)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
}, "5.17")

var haveEnum64 = internal.NewFeatureTest("ENUM64", func() error {
if err := haveBTF(); err != nil {
}, "5.17")

var haveEnum64 = internal.NewFeatureTest("ENUM64",
func(opts ...internal.FeatureTestOption) error {
if err := haveBTF(opts...); err != nil {
return err
}

enum := &Enum{
Size: 8,
Values: []EnumValue{
{"TEST", math.MaxUint32 + 1},
},
}

o := internal.BuildOptions(opts...)
err := probeBTF(enum, o.BpffsTokenFd)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
}
}, "6.0")

enum := &Enum{
Size: 8,
Values: []EnumValue{
{"TEST", math.MaxUint32 + 1},
},
}

err := probeBTF(enum)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
}, "6.0")

func probeBTF(typ Type) error {
func probeBTF(typ Type, tokenFd int32) error {
b, err := NewBuilder([]Type{typ})
if err != nil {
return err
Expand All @@ -145,11 +162,17 @@ func probeBTF(typ Type) error {
return err
}

fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
attr := &sys.BtfLoadAttr{
Btf: sys.SlicePointer(buf),
BtfSize: uint32(len(buf)),
})
}

if tokenFd > 0 {
attr.BtfTokenFd = tokenFd
attr.BtfFlags |= sys.BPF_F_TOKEN_FD
}

fd, err := sys.BtfLoad(attr)
if err == nil {
fd.Close()
}
Expand Down
Loading