summaryrefslogtreecommitdiff
path: root/vendor/github.com/cilium/ebpf/prog.go
diff options
context:
space:
mode:
authorLibravatar dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>2023-04-03 11:16:17 +0200
committerLibravatar GitHub <noreply@github.com>2023-04-03 11:16:17 +0200
commit57dc742c76d7876a2457594715a7b5bc2c9a92bd (patch)
tree76be1ec744face5bf4f617d4c9fca084707e4268 /vendor/github.com/cilium/ebpf/prog.go
parent[bugfix/frontend] Preload css styles (#1638) (diff)
downloadgotosocial-57dc742c76d7876a2457594715a7b5bc2c9a92bd.tar.xz
[chore]: Bump github.com/KimMachineGun/automemlimit from 0.2.4 to 0.2.5 (#1666)
Bumps [github.com/KimMachineGun/automemlimit](https://github.com/KimMachineGun/automemlimit) from 0.2.4 to 0.2.5. - [Release notes](https://github.com/KimMachineGun/automemlimit/releases) - [Commits](https://github.com/KimMachineGun/automemlimit/compare/v0.2.4...v0.2.5) --- updated-dependencies: - dependency-name: github.com/KimMachineGun/automemlimit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Diffstat (limited to 'vendor/github.com/cilium/ebpf/prog.go')
-rw-r--r--vendor/github.com/cilium/ebpf/prog.go675
1 files changed, 426 insertions, 249 deletions
diff --git a/vendor/github.com/cilium/ebpf/prog.go b/vendor/github.com/cilium/ebpf/prog.go
index 4b65f23b2..675edc711 100644
--- a/vendor/github.com/cilium/ebpf/prog.go
+++ b/vendor/github.com/cilium/ebpf/prog.go
@@ -7,12 +7,14 @@ import (
"fmt"
"math"
"path/filepath"
+ "runtime"
"strings"
"time"
"github.com/cilium/ebpf/asm"
+ "github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
- "github.com/cilium/ebpf/internal/btf"
+ "github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
@@ -41,6 +43,13 @@ type ProgramOptions struct {
// Controls the output buffer size for the verifier. Defaults to
// DefaultVerifierLogSize.
LogSize int
+ // Type information used for CO-RE relocations and when attaching to
+ // kernel functions.
+ //
+ // This is useful in environments where the kernel BTF is not available
+ // (containers) or where it is in a non-standard location. Defaults to
+ // use the kernel BTF from a well-known location if nil.
+ KernelTypes *btf.Spec
}
// ProgramSpec defines a Program.
@@ -48,29 +57,48 @@ type ProgramSpec struct {
// Name is passed to the kernel as a debug aid. Must only contain
// alpha numeric and '_' characters.
Name string
+
// Type determines at which hook in the kernel a program will run.
- Type ProgramType
+ Type ProgramType
+
+ // AttachType of the program, needed to differentiate allowed context
+ // accesses in some newer program types like CGroupSockAddr.
+ //
+ // Available on kernels 4.17 and later.
AttachType AttachType
- // Name of a kernel data structure to attach to. It's interpretation
- // depends on Type and AttachType.
- AttachTo string
+
+ // Name of a kernel data structure or function to attach to. Its
+ // interpretation depends on Type and AttachType.
+ AttachTo string
+
+ // The program to attach to. Must be provided manually.
+ AttachTarget *Program
+
+ // The name of the ELF section this program orininated from.
+ SectionName string
+
Instructions asm.Instructions
+ // Flags is passed to the kernel and specifies additional program
+ // load attributes.
+ Flags uint32
+
// License of the program. Some helpers are only available if
// the license is deemed compatible with the GPL.
//
// See https://www.kernel.org/doc/html/latest/process/license-rules.html#id1
License string
- // Version used by tracing programs.
+ // Version used by Kprobe programs.
//
- // Deprecated: superseded by BTF.
+ // Deprecated on kernels 5.0 and later. Leave empty to let the library
+ // detect this value automatically.
KernelVersion uint32
// The BTF associated with this program. Changing Instructions
// will most likely invalidate the contained data, and may
// result in errors when attempting to load it into the kernel.
- BTF *btf.Program
+ BTF *btf.Spec
// The byte order this program was compiled for, may be nil.
ByteOrder binary.ByteOrder
@@ -95,6 +123,8 @@ func (ps *ProgramSpec) Tag() (string, error) {
return ps.Instructions.Tag(internal.NativeEndian)
}
+type VerifierError = internal.VerifierError
+
// Program represents BPF program loaded into the kernel.
//
// It is not safe to close a Program which is used by other goroutines.
@@ -103,7 +133,7 @@ type Program struct {
// otherwise it is empty.
VerifierLog string
- fd *internal.FD
+ fd *sys.FD
name string
pinnedPath string
typ ProgramType
@@ -111,8 +141,7 @@ type Program struct {
// NewProgram creates a new Program.
//
-// Loading a program for the first time will perform
-// feature detection by loading small, temporary programs.
+// See NewProgramWithOptions for details.
func NewProgram(spec *ProgramSpec) (*Program, error) {
return NewProgramWithOptions(spec, ProgramOptions{})
}
@@ -121,97 +150,129 @@ func NewProgram(spec *ProgramSpec) (*Program, error) {
//
// Loading a program for the first time will perform
// feature detection by loading small, temporary programs.
+//
+// Returns an error wrapping VerifierError if the program or its BTF is rejected
+// by the kernel.
func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, error) {
- btfs := make(btfHandleCache)
- defer btfs.close()
+ if spec == nil {
+ return nil, errors.New("can't load a program from a nil spec")
+ }
+
+ handles := newHandleCache()
+ defer handles.close()
- return newProgramWithOptions(spec, opts, btfs)
+ prog, err := newProgramWithOptions(spec, opts, handles)
+ if errors.Is(err, asm.ErrUnsatisfiedMapReference) {
+ return nil, fmt.Errorf("cannot load program without loading its whole collection: %w", err)
+ }
+ return prog, err
}
-func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, btfs btfHandleCache) (*Program, error) {
+func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *handleCache) (*Program, error) {
if len(spec.Instructions) == 0 {
- return nil, errors.New("Instructions cannot be empty")
+ return nil, errors.New("instructions cannot be empty")
}
- if len(spec.License) == 0 {
- return nil, errors.New("License cannot be empty")
+ if spec.Type == UnspecifiedProgram {
+ return nil, errors.New("can't load program of unspecified type")
}
if spec.ByteOrder != nil && spec.ByteOrder != internal.NativeEndian {
return nil, fmt.Errorf("can't load %s program on %s", spec.ByteOrder, internal.NativeEndian)
}
- insns := make(asm.Instructions, len(spec.Instructions))
- copy(insns, spec.Instructions)
-
- if err := fixupJumpsAndCalls(insns); err != nil {
- return nil, err
- }
-
- buf := bytes.NewBuffer(make([]byte, 0, len(spec.Instructions)*asm.InstructionSize))
- err := insns.Marshal(buf, internal.NativeEndian)
- if err != nil {
- return nil, err
+ // Kernels before 5.0 (6c4fc209fcf9 "bpf: remove useless version check for prog load")
+ // require the version field to be set to the value of the KERNEL_VERSION
+ // macro for kprobe-type programs.
+ // Overwrite Kprobe program version if set to zero or the magic version constant.
+ kv := spec.KernelVersion
+ if spec.Type == Kprobe && (kv == 0 || kv == internal.MagicKernelVersion) {
+ v, err := internal.KernelVersion()
+ if err != nil {
+ return nil, fmt.Errorf("detecting kernel version: %w", err)
+ }
+ kv = v.Kernel()
}
- bytecode := buf.Bytes()
- insCount := uint32(len(bytecode) / asm.InstructionSize)
- attr := &bpfProgLoadAttr{
- progType: spec.Type,
- expectedAttachType: spec.AttachType,
- insCount: insCount,
- instructions: internal.NewSlicePointer(bytecode),
- license: internal.NewStringPointer(spec.License),
- kernelVersion: spec.KernelVersion,
+ attr := &sys.ProgLoadAttr{
+ ProgType: sys.ProgType(spec.Type),
+ ProgFlags: spec.Flags,
+ ExpectedAttachType: sys.AttachType(spec.AttachType),
+ License: sys.NewStringPointer(spec.License),
+ KernVersion: kv,
}
if haveObjName() == nil {
- attr.progName = newBPFObjName(spec.Name)
+ attr.ProgName = sys.NewObjName(spec.Name)
}
+ kernelTypes := opts.KernelTypes
+
+ insns := make(asm.Instructions, len(spec.Instructions))
+ copy(insns, spec.Instructions)
+
var btfDisabled bool
if spec.BTF != nil {
- if relos, err := btf.ProgramRelocations(spec.BTF, nil); err != nil {
- return nil, fmt.Errorf("CO-RE relocations: %s", err)
- } else if len(relos) > 0 {
- return nil, fmt.Errorf("applying CO-RE relocations: %w", ErrNotSupported)
+ if err := applyRelocations(insns, spec.BTF, kernelTypes); err != nil {
+ return nil, fmt.Errorf("apply CO-RE relocations: %w", err)
}
- handle, err := btfs.load(btf.ProgramSpec(spec.BTF))
+ handle, err := handles.btfHandle(spec.BTF)
btfDisabled = errors.Is(err, btf.ErrNotSupported)
if err != nil && !btfDisabled {
return nil, fmt.Errorf("load BTF: %w", err)
}
if handle != nil {
- attr.progBTFFd = uint32(handle.FD())
+ attr.ProgBtfFd = uint32(handle.FD())
- recSize, bytes, err := btf.ProgramLineInfos(spec.BTF)
+ fib, lib, err := btf.MarshalExtInfos(insns, spec.BTF.TypeID)
if err != nil {
- return nil, fmt.Errorf("get BTF line infos: %w", err)
+ return nil, err
}
- attr.lineInfoRecSize = recSize
- attr.lineInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize))
- attr.lineInfo = internal.NewSlicePointer(bytes)
- recSize, bytes, err = btf.ProgramFuncInfos(spec.BTF)
- if err != nil {
- return nil, fmt.Errorf("get BTF function infos: %w", err)
- }
- attr.funcInfoRecSize = recSize
- attr.funcInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize))
- attr.funcInfo = internal.NewSlicePointer(bytes)
+ attr.FuncInfoRecSize = btf.FuncInfoSize
+ attr.FuncInfoCnt = uint32(len(fib)) / btf.FuncInfoSize
+ attr.FuncInfo = sys.NewSlicePointer(fib)
+
+ attr.LineInfoRecSize = btf.LineInfoSize
+ attr.LineInfoCnt = uint32(len(lib)) / btf.LineInfoSize
+ attr.LineInfo = sys.NewSlicePointer(lib)
}
}
- if spec.AttachTo != "" {
- target, err := resolveBTFType(spec.AttachTo, spec.Type, spec.AttachType)
+ if err := fixupAndValidate(insns); err != nil {
+ return nil, err
+ }
+
+ buf := bytes.NewBuffer(make([]byte, 0, insns.Size()))
+ err := insns.Marshal(buf, internal.NativeEndian)
+ if err != nil {
+ return nil, err
+ }
+
+ bytecode := buf.Bytes()
+ attr.Insns = sys.NewSlicePointer(bytecode)
+ attr.InsnCnt = uint32(len(bytecode) / asm.InstructionSize)
+
+ if spec.AttachTarget != nil {
+ targetID, err := findTargetInProgram(spec.AttachTarget, spec.AttachTo, spec.Type, spec.AttachType)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("attach %s/%s: %w", spec.Type, spec.AttachType, err)
}
- if target != nil {
- attr.attachBTFID = target.ID()
+
+ attr.AttachBtfId = uint32(targetID)
+ attr.AttachProgFd = uint32(spec.AttachTarget.FD())
+ defer runtime.KeepAlive(spec.AttachTarget)
+ } else if spec.AttachTo != "" {
+ targetID, err := findTargetInKernel(kernelTypes, spec.AttachTo, spec.Type, spec.AttachType)
+ if err != nil && !errors.Is(err, errUnrecognizedAttachType) {
+ // We ignore errUnrecognizedAttachType since AttachTo may be non-empty
+ // for programs that don't attach anywhere.
+ return nil, fmt.Errorf("attach %s/%s: %w", spec.Type, spec.AttachType, err)
}
+
+ attr.AttachBtfId = uint32(targetID)
}
logSize := DefaultVerifierLogSize
@@ -222,36 +283,46 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, btfs btfHandl
var logBuf []byte
if opts.LogLevel > 0 {
logBuf = make([]byte, logSize)
- attr.logLevel = opts.LogLevel
- attr.logSize = uint32(len(logBuf))
- attr.logBuf = internal.NewSlicePointer(logBuf)
+ attr.LogLevel = opts.LogLevel
+ attr.LogSize = uint32(len(logBuf))
+ attr.LogBuf = sys.NewSlicePointer(logBuf)
}
- fd, err := bpfProgLoad(attr)
+ fd, err := sys.ProgLoad(attr)
if err == nil {
- return &Program{internal.CString(logBuf), fd, spec.Name, "", spec.Type}, nil
+ return &Program{unix.ByteSliceToString(logBuf), fd, spec.Name, "", spec.Type}, nil
}
- logErr := err
- if opts.LogLevel == 0 {
+ if opts.LogLevel == 0 && opts.LogSize >= 0 {
// Re-run with the verifier enabled to get better error messages.
logBuf = make([]byte, logSize)
- attr.logLevel = 1
- attr.logSize = uint32(len(logBuf))
- attr.logBuf = internal.NewSlicePointer(logBuf)
+ attr.LogLevel = 1
+ attr.LogSize = uint32(len(logBuf))
+ attr.LogBuf = sys.NewSlicePointer(logBuf)
+ _, _ = sys.ProgLoad(attr)
+ }
+
+ switch {
+ case errors.Is(err, unix.EPERM):
+ if len(logBuf) > 0 && logBuf[0] == 0 {
+ // EPERM due to RLIMIT_MEMLOCK happens before the verifier, so we can
+ // check that the log is empty to reduce false positives.
+ return nil, fmt.Errorf("load program: %w (MEMLOCK may be too low, consider rlimit.RemoveMemlock)", err)
+ }
- _, logErr = bpfProgLoad(attr)
- }
+ fallthrough
- if errors.Is(logErr, unix.EPERM) && logBuf[0] == 0 {
- // EPERM due to RLIMIT_MEMLOCK happens before the verifier, so we can
- // check that the log is empty to reduce false positives.
- return nil, fmt.Errorf("load program: RLIMIT_MEMLOCK may be too low: %w", logErr)
+ case errors.Is(err, unix.EINVAL):
+ if hasFunctionReferences(spec.Instructions) {
+ if err := haveBPFToBPFCalls(); err != nil {
+ return nil, fmt.Errorf("load program: %w", err)
+ }
+ }
}
- err = internal.ErrorWithLog(err, logBuf, logErr)
+ err = internal.ErrorWithLog(err, logBuf)
if btfDisabled {
- return nil, fmt.Errorf("load program without BTF: %w", err)
+ return nil, fmt.Errorf("load program: %w (BTF disabled)", err)
}
return nil, fmt.Errorf("load program: %w", err)
}
@@ -262,18 +333,21 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, btfs btfHandl
//
// Requires at least Linux 4.10.
func NewProgramFromFD(fd int) (*Program, error) {
- if fd < 0 {
- return nil, errors.New("invalid fd")
+ f, err := sys.NewFD(fd)
+ if err != nil {
+ return nil, err
}
- return newProgramFromFD(internal.NewFD(uint32(fd)))
+ return newProgramFromFD(f)
}
// 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(internal.BPF_PROG_GET_FD_BY_ID, uint32(id))
+ fd, err := sys.ProgGetFdById(&sys.ProgGetFdByIdAttr{
+ Id: uint32(id),
+ })
if err != nil {
return nil, fmt.Errorf("get program by id: %w", err)
}
@@ -281,7 +355,7 @@ func NewProgramFromID(id ProgramID) (*Program, error) {
return newProgramFromFD(fd)
}
-func newProgramFromFD(fd *internal.FD) (*Program, error) {
+func newProgramFromFD(fd *sys.FD) (*Program, error) {
info, err := newProgramInfoFromFd(fd)
if err != nil {
fd.Close()
@@ -310,18 +384,29 @@ func (p *Program) Info() (*ProgramInfo, error) {
return newProgramInfoFromFd(p.fd)
}
-// FD gets the file descriptor of the Program.
+// Handle returns a reference to the program's type information in the kernel.
//
-// It is invalid to call this function after Close has been called.
-func (p *Program) FD() int {
- fd, err := p.fd.Value()
+// Returns ErrNotSupported if the kernel has no BTF support, or if there is no
+// BTF associated with the program.
+func (p *Program) Handle() (*btf.Handle, error) {
+ info, err := p.Info()
if err != nil {
- // Best effort: -1 is the number most likely to be an
- // invalid file descriptor.
- return -1
+ return nil, err
}
- return int(fd)
+ id, ok := info.BTFID()
+ if !ok {
+ return nil, fmt.Errorf("program %s: retrieve BTF ID: %w", p, ErrNotSupported)
+ }
+
+ return btf.NewHandleFromID(id)
+}
+
+// FD gets the file descriptor of the Program.
+//
+// It is invalid to call this function after Close has been called.
+func (p *Program) FD() int {
+ return p.fd.Int()
}
// Clone creates a duplicate of the Program.
@@ -345,9 +430,12 @@ func (p *Program) Clone() (*Program, error) {
// Pin persists the Program on the BPF virtual file system past the lifetime of
// the process that created it
//
+// Calling Pin on a previously pinned program will overwrite the path, except when
+// the new path already exists. Re-pinning across filesystems is not supported.
+//
// This requires bpffs to be mounted above fileName. See https://docs.cilium.io/en/k8s-doc/admin/#admin-mount-bpffs
func (p *Program) Pin(fileName string) error {
- if err := pin(p.pinnedPath, fileName, p.fd); err != nil {
+ if err := internal.Pin(p.pinnedPath, fileName, p.fd); err != nil {
return err
}
p.pinnedPath = fileName
@@ -360,7 +448,7 @@ func (p *Program) Pin(fileName string) error {
//
// Unpinning an unpinned Program returns nil.
func (p *Program) Unpin() error {
- if err := unpin(p.pinnedPath); err != nil {
+ if err := internal.Unpin(p.pinnedPath); err != nil {
return err
}
p.pinnedPath = ""
@@ -369,13 +457,12 @@ func (p *Program) Unpin() error {
// IsPinned returns true if the Program has a non-empty pinned path.
func (p *Program) IsPinned() bool {
- if p.pinnedPath == "" {
- return false
- }
- return true
+ return p.pinnedPath != ""
}
-// Close unloads the program from the kernel.
+// Close the Program's underlying file descriptor, which could unload
+// the program from the kernel if it is not pinned or attached to a
+// kernel hook.
func (p *Program) Close() error {
if p == nil {
return nil
@@ -384,6 +471,28 @@ func (p *Program) Close() error {
return p.fd.Close()
}
+// Various options for Run'ing a Program
+type RunOptions struct {
+ // Program's data input. Required field.
+ Data []byte
+ // Program's data after Program has run. Caller must allocate. Optional field.
+ DataOut []byte
+ // Program's context input. Optional field.
+ Context interface{}
+ // Program's context after Program has run. Must be a pointer or slice. Optional field.
+ ContextOut interface{}
+ // Number of times to run Program. Optional field. Defaults to 1.
+ Repeat uint32
+ // Optional flags.
+ Flags uint32
+ // CPU to run Program on. Optional field.
+ // Note not all program types support this field.
+ CPU uint32
+ // Called whenever the syscall is interrupted, and should be set to testing.B.ResetTimer
+ // or similar. Typically used during benchmarking. Optional field.
+ Reset func()
+}
+
// Test runs the Program in the kernel with the given input and returns the
// value returned by the eBPF program. outLen may be zero.
//
@@ -392,11 +501,38 @@ func (p *Program) Close() error {
//
// This function requires at least Linux 4.12.
func (p *Program) Test(in []byte) (uint32, []byte, error) {
- ret, out, _, err := p.testRun(in, 1, nil)
+ // Older kernels ignore the dataSizeOut argument when copying to user space.
+ // Combined with things like bpf_xdp_adjust_head() we don't really know what the final
+ // size will be. Hence we allocate an output buffer which we hope will always be large
+ // enough, and panic if the kernel wrote past the end of the allocation.
+ // See https://patchwork.ozlabs.org/cover/1006822/
+ var out []byte
+ if len(in) > 0 {
+ out = make([]byte, len(in)+outputPad)
+ }
+
+ opts := RunOptions{
+ Data: in,
+ DataOut: out,
+ Repeat: 1,
+ }
+
+ ret, _, err := p.testRun(&opts)
if err != nil {
return ret, nil, fmt.Errorf("can't test program: %w", err)
}
- return ret, out, nil
+ return ret, opts.DataOut, nil
+}
+
+// Run runs the Program in kernel with given RunOptions.
+//
+// Note: the same restrictions from Test apply.
+func (p *Program) Run(opts *RunOptions) (uint32, error) {
+ ret, _, err := p.testRun(opts)
+ if err != nil {
+ return ret, fmt.Errorf("can't test program: %w", err)
+ }
+ return ret, nil
}
// Benchmark runs the Program with the given input for a number of times
@@ -411,7 +547,17 @@ func (p *Program) Test(in []byte) (uint32, []byte, error) {
//
// This function requires at least Linux 4.12.
func (p *Program) Benchmark(in []byte, repeat int, reset func()) (uint32, time.Duration, error) {
- ret, _, total, err := p.testRun(in, repeat, reset)
+ if uint(repeat) > math.MaxUint32 {
+ return 0, 0, fmt.Errorf("repeat is too high")
+ }
+
+ opts := RunOptions{
+ Data: in,
+ Repeat: uint32(repeat),
+ Reset: reset,
+ }
+
+ ret, total, err := p.testRun(&opts)
if err != nil {
return ret, total, fmt.Errorf("can't benchmark program: %w", err)
}
@@ -420,6 +566,7 @@ func (p *Program) Benchmark(in []byte, repeat int, reset func()) (uint32, time.D
var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() error {
prog, err := NewProgram(&ProgramSpec{
+ // SocketFilter does not require privileges on newer kernels.
Type: SocketFilter,
Instructions: asm.Instructions{
asm.LoadImm(asm.R0, 0, asm.DWord),
@@ -435,88 +582,109 @@ var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() e
// Programs require at least 14 bytes input
in := make([]byte, 14)
- attr := bpfProgTestRunAttr{
- fd: uint32(prog.FD()),
- dataSizeIn: uint32(len(in)),
- dataIn: internal.NewSlicePointer(in),
+ attr := sys.ProgRunAttr{
+ ProgFd: uint32(prog.FD()),
+ DataSizeIn: uint32(len(in)),
+ DataIn: sys.NewSlicePointer(in),
}
- err = bpfProgTestRun(&attr)
- if errors.Is(err, unix.EINVAL) {
+ err = sys.ProgRun(&attr)
+ switch {
+ case errors.Is(err, unix.EINVAL):
// Check for EINVAL specifically, rather than err != nil since we
// otherwise misdetect due to insufficient permissions.
return internal.ErrNotSupported
- }
- if errors.Is(err, unix.EINTR) {
+
+ case errors.Is(err, unix.EINTR):
// We know that PROG_TEST_RUN is supported if we get EINTR.
return nil
- }
- return err
-})
-func (p *Program) testRun(in []byte, repeat int, reset func()) (uint32, []byte, time.Duration, error) {
- if uint(repeat) > math.MaxUint32 {
- return 0, nil, 0, fmt.Errorf("repeat is too high")
+ case errors.Is(err, unix.ENOTSUPP):
+ // The first PROG_TEST_RUN patches shipped in 4.12 didn't include
+ // a test runner for SocketFilter. ENOTSUPP means PROG_TEST_RUN is
+ // supported, but not for the program type used in the probe.
+ return nil
}
- if len(in) == 0 {
- return 0, nil, 0, fmt.Errorf("missing input")
- }
+ return err
+})
- if uint(len(in)) > math.MaxUint32 {
- return 0, nil, 0, fmt.Errorf("input is too long")
+func (p *Program) testRun(opts *RunOptions) (uint32, time.Duration, error) {
+ if uint(len(opts.Data)) > math.MaxUint32 {
+ return 0, 0, fmt.Errorf("input is too long")
}
if err := haveProgTestRun(); err != nil {
- return 0, nil, 0, err
+ return 0, 0, err
}
- // Older kernels ignore the dataSizeOut argument when copying to user space.
- // Combined with things like bpf_xdp_adjust_head() we don't really know what the final
- // size will be. Hence we allocate an output buffer which we hope will always be large
- // enough, and panic if the kernel wrote past the end of the allocation.
- // See https://patchwork.ozlabs.org/cover/1006822/
- out := make([]byte, len(in)+outputPad)
+ var ctxBytes []byte
+ if opts.Context != nil {
+ ctx := new(bytes.Buffer)
+ if err := binary.Write(ctx, internal.NativeEndian, opts.Context); err != nil {
+ return 0, 0, fmt.Errorf("cannot serialize context: %v", err)
+ }
+ ctxBytes = ctx.Bytes()
+ }
- fd, err := p.fd.Value()
- if err != nil {
- return 0, nil, 0, err
+ var ctxOut []byte
+ if opts.ContextOut != nil {
+ ctxOut = make([]byte, binary.Size(opts.ContextOut))
}
- attr := bpfProgTestRunAttr{
- fd: fd,
- dataSizeIn: uint32(len(in)),
- dataSizeOut: uint32(len(out)),
- dataIn: internal.NewSlicePointer(in),
- dataOut: internal.NewSlicePointer(out),
- repeat: uint32(repeat),
+ attr := sys.ProgRunAttr{
+ ProgFd: p.fd.Uint(),
+ DataSizeIn: uint32(len(opts.Data)),
+ DataSizeOut: uint32(len(opts.DataOut)),
+ DataIn: sys.NewSlicePointer(opts.Data),
+ DataOut: sys.NewSlicePointer(opts.DataOut),
+ Repeat: uint32(opts.Repeat),
+ CtxSizeIn: uint32(len(ctxBytes)),
+ CtxSizeOut: uint32(len(ctxOut)),
+ CtxIn: sys.NewSlicePointer(ctxBytes),
+ CtxOut: sys.NewSlicePointer(ctxOut),
+ Flags: opts.Flags,
+ Cpu: opts.CPU,
}
for {
- err = bpfProgTestRun(&attr)
+ err := sys.ProgRun(&attr)
if err == nil {
break
}
if errors.Is(err, unix.EINTR) {
- if reset != nil {
- reset()
+ if opts.Reset != nil {
+ opts.Reset()
}
continue
}
- return 0, nil, 0, fmt.Errorf("can't run test: %w", err)
+ if errors.Is(err, unix.ENOTSUPP) {
+ return 0, 0, fmt.Errorf("kernel doesn't support testing program type %s: %w", p.Type(), ErrNotSupported)
+ }
+
+ return 0, 0, fmt.Errorf("can't run test: %w", err)
+ }
+
+ if opts.DataOut != nil {
+ if int(attr.DataSizeOut) > cap(opts.DataOut) {
+ // Houston, we have a problem. The program created more data than we allocated,
+ // and the kernel wrote past the end of our buffer.
+ panic("kernel wrote past end of output buffer")
+ }
+ opts.DataOut = opts.DataOut[:int(attr.DataSizeOut)]
}
- if int(attr.dataSizeOut) > cap(out) {
- // Houston, we have a problem. The program created more data than we allocated,
- // and the kernel wrote past the end of our buffer.
- panic("kernel wrote past end of output buffer")
+ if len(ctxOut) != 0 {
+ b := bytes.NewReader(ctxOut)
+ if err := binary.Read(b, internal.NativeEndian, opts.ContextOut); err != nil {
+ return 0, 0, fmt.Errorf("failed to decode ContextOut: %v", err)
+ }
}
- out = out[:int(attr.dataSizeOut)]
- total := time.Duration(attr.duration) * time.Nanosecond
- return attr.retval, out, total, nil
+ total := time.Duration(attr.Duration) * time.Nanosecond
+ return attr.Retval, total, nil
}
func unmarshalProgram(buf []byte) (*Program, error) {
@@ -535,70 +703,19 @@ func marshalProgram(p *Program, length int) ([]byte, error) {
return nil, fmt.Errorf("can't marshal program to %d bytes", length)
}
- value, err := p.fd.Value()
- if err != nil {
- return nil, err
- }
-
buf := make([]byte, 4)
- internal.NativeEndian.PutUint32(buf, value)
+ internal.NativeEndian.PutUint32(buf, p.fd.Uint())
return buf, nil
}
-// Attach a Program.
-//
-// Deprecated: use link.RawAttachProgram instead.
-func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error {
- if fd < 0 {
- return errors.New("invalid fd")
- }
-
- pfd, err := p.fd.Value()
- if err != nil {
- return err
- }
-
- attr := internal.BPFProgAttachAttr{
- TargetFd: uint32(fd),
- AttachBpfFd: pfd,
- AttachType: uint32(typ),
- AttachFlags: uint32(flags),
- }
-
- return internal.BPFProgAttach(&attr)
-}
-
-// Detach a Program.
-//
-// Deprecated: use link.RawDetachProgram instead.
-func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error {
- if fd < 0 {
- return errors.New("invalid fd")
- }
-
- if flags != 0 {
- return errors.New("flags must be zero")
- }
-
- pfd, err := p.fd.Value()
- if err != nil {
- return err
- }
-
- attr := internal.BPFProgDetachAttr{
- TargetFd: uint32(fd),
- AttachBpfFd: pfd,
- AttachType: uint32(typ),
- }
-
- return internal.BPFProgDetach(&attr)
-}
-
// LoadPinnedProgram loads a Program from a BPF file.
//
// Requires at least Linux 4.11.
-func LoadPinnedProgram(fileName string) (*Program, error) {
- fd, err := internal.BPFObjGet(fileName)
+func LoadPinnedProgram(fileName string, opts *LoadPinOptions) (*Program, error) {
+ fd, err := sys.ObjGet(&sys.ObjGetAttr{
+ Pathname: sys.NewStringPointer(fileName),
+ FileFlags: opts.Marshal(),
+ })
if err != nil {
return nil, err
}
@@ -609,7 +726,7 @@ func LoadPinnedProgram(fileName string) (*Program, error) {
return nil, fmt.Errorf("info for %s: %w", fileName, err)
}
- return &Program{"", fd, filepath.Base(fileName), "", info.Type}, nil
+ return &Program{"", fd, filepath.Base(fileName), fileName, info.Type}, nil
}
// SanitizeName replaces all invalid characters in name with replacement.
@@ -632,67 +749,127 @@ func SanitizeName(name string, replacement rune) string {
//
// Returns ErrNotExist, if there is no next eBPF program.
func ProgramGetNextID(startID ProgramID) (ProgramID, error) {
- id, err := objGetNextID(internal.BPF_PROG_GET_NEXT_ID, uint32(startID))
- return ProgramID(id), err
+ attr := &sys.ProgGetNextIdAttr{Id: uint32(startID)}
+ return ProgramID(attr.NextId), sys.ProgGetNextId(attr)
}
-// ID returns the systemwide unique ID of the program.
+// BindMap binds map to the program and is only released once program is released.
//
-// Deprecated: use ProgramInfo.ID() instead.
-func (p *Program) ID() (ProgramID, error) {
- info, err := bpfGetProgInfoByFD(p.fd)
- if err != nil {
- return ProgramID(0), err
- }
- return ProgramID(info.id), nil
-}
-
-func findKernelType(name string, typ btf.Type) error {
- kernel, err := btf.LoadKernelSpec()
- if err != nil {
- return fmt.Errorf("can't load kernel spec: %w", err)
+// This may be used in cases where metadata should be associated with the program
+// which otherwise does not contain any references to the map.
+func (p *Program) BindMap(m *Map) error {
+ attr := &sys.ProgBindMapAttr{
+ ProgFd: uint32(p.FD()),
+ MapFd: uint32(m.FD()),
}
- return kernel.FindType(name, typ)
+ return sys.ProgBindMap(attr)
}
-func resolveBTFType(name string, progType ProgramType, attachType AttachType) (btf.Type, error) {
+var errUnrecognizedAttachType = errors.New("unrecognized attach type")
+
+// find an attach target type in the kernel.
+//
+// spec may be nil and defaults to the canonical kernel BTF. name together with
+// progType and attachType determine which type we need to attach to.
+//
+// Returns errUnrecognizedAttachType.
+func findTargetInKernel(spec *btf.Spec, name string, progType ProgramType, attachType AttachType) (btf.TypeID, error) {
type match struct {
p ProgramType
a AttachType
}
- target := match{progType, attachType}
- switch target {
+ var (
+ typeName, featureName string
+ isBTFTypeFunc = true
+ )
+
+ switch (match{progType, attachType}) {
case match{LSM, AttachLSMMac}:
- var target btf.Func
- err := findKernelType("bpf_lsm_"+name, &target)
- if errors.Is(err, btf.ErrNotFound) {
- return nil, &internal.UnsupportedFeatureError{
- Name: name + " LSM hook",
- }
- }
- if err != nil {
- return nil, fmt.Errorf("resolve BTF for LSM hook %s: %w", name, err)
- }
+ typeName = "bpf_lsm_" + name
+ featureName = name + " LSM hook"
+ case match{Tracing, AttachTraceIter}:
+ typeName = "bpf_iter_" + name
+ featureName = name + " iterator"
+ case match{Tracing, AttachTraceFEntry}:
+ typeName = name
+ featureName = fmt.Sprintf("fentry %s", name)
+ case match{Tracing, AttachTraceFExit}:
+ typeName = name
+ featureName = fmt.Sprintf("fexit %s", name)
+ case match{Tracing, AttachModifyReturn}:
+ typeName = name
+ featureName = fmt.Sprintf("fmod_ret %s", name)
+ case match{Tracing, AttachTraceRawTp}:
+ typeName = fmt.Sprintf("btf_trace_%s", name)
+ featureName = fmt.Sprintf("raw_tp %s", name)
+ isBTFTypeFunc = false
+ default:
+ return 0, errUnrecognizedAttachType
+ }
- return &target, nil
+ spec, err := maybeLoadKernelBTF(spec)
+ if err != nil {
+ return 0, fmt.Errorf("load kernel spec: %w", err)
+ }
- case match{Tracing, AttachTraceIter}:
- var target btf.Func
- err := findKernelType("bpf_iter_"+name, &target)
+ var target btf.Type
+ if isBTFTypeFunc {
+ var targetFunc *btf.Func
+ err = spec.TypeByName(typeName, &targetFunc)
+ target = targetFunc
+ } else {
+ var targetTypedef *btf.Typedef
+ err = spec.TypeByName(typeName, &targetTypedef)
+ target = targetTypedef
+ }
+
+ if err != nil {
if errors.Is(err, btf.ErrNotFound) {
- return nil, &internal.UnsupportedFeatureError{
- Name: name + " iterator",
+ return 0, &internal.UnsupportedFeatureError{
+ Name: featureName,
}
}
- if err != nil {
- return nil, fmt.Errorf("resolve BTF for iterator %s: %w", name, err)
- }
+ return 0, fmt.Errorf("find target for %s: %w", featureName, err)
+ }
+
+ return spec.TypeID(target)
+}
- return &target, nil
+// find an attach target type in a program.
+//
+// Returns errUnrecognizedAttachType.
+func findTargetInProgram(prog *Program, name string, progType ProgramType, attachType AttachType) (btf.TypeID, error) {
+ type match struct {
+ p ProgramType
+ a AttachType
+ }
+ var typeName string
+ switch (match{progType, attachType}) {
+ case match{Extension, AttachNone}:
+ typeName = name
default:
- return nil, nil
+ return 0, errUnrecognizedAttachType
+ }
+
+ btfHandle, err := prog.Handle()
+ if err != nil {
+ return 0, fmt.Errorf("load target BTF: %w", err)
}
+ defer btfHandle.Close()
+
+ spec, err := btfHandle.Spec(nil)
+ if err != nil {
+ return 0, err
+ }
+
+ var targetFunc *btf.Func
+ err = spec.TypeByName(typeName, &targetFunc)
+ if err != nil {
+ return 0, fmt.Errorf("find target %s: %w", typeName, err)
+ }
+
+ return spec.TypeID(targetFunc)
}