Skip to content

Commit

Permalink
features: add macros probes
Browse files Browse the repository at this point in the history
Signed-off-by: Florian Lehner <dev@der-flo.net>
  • Loading branch information
florianl committed Jan 8, 2022
1 parent 70d770f commit adcf979
Show file tree
Hide file tree
Showing 3 changed files with 271 additions and 0 deletions.
206 changes: 206 additions & 0 deletions features/macros.go
@@ -0,0 +1,206 @@
package features

import (
"bytes"
"errors"
"fmt"
"os"
"sync"

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

func init() {
macros.macroTypes = make(map[MacroType]error, maxMacroType)
}

var (
macros macroCache
)

type macroCache struct {
sync.Mutex
macroTypes map[MacroType]error
}

type MacroType uint32

// Max returns the latest supported MacroType.
func (_ MacroType) Max() MacroType {
return maxMacroType - 1
}

const (
// LargeInsn support introduced in
// commit c04c0d2b968ac45d6ef020316808ef6c82325a82
LargeInsn MacroType = iota
// BoundedLoops support introduced in
// commit 2589726d12a1b12eaaa93c7f1ea64287e383c7a5
BoundedLoops
// V2ISA support introduced in
// commit 92b31a9af73b3a3fc801899335d6c47966351830
V2ISA
// V3ISA support introduced in
// commit 092ed0968bb648cd18e8a0430cd0a8a71727315c
V3ISA
// maxFeatureType - Bound enum of FeatureTypes, has to be last in enum.
maxMacroType
)

const (
maxInsns = 4096
)

// Supports probes the running kernel for the availability of the specified eBPF feature.
// Return values have the following semantics:
//
// err == nil: The feature is available.
// errors.Is(err, ebpf.ErrNotSupported): The feature is not available.
// err != nil: Any errors encountered during probe execution, wrapped.
//
// Note that the latter case may include false negatives, and that program creation may
// succeed despite an error being returned. Some program types cannot reliably be probed and
// will also return error. Only `nil` and `ebpf.ErrNotSupported` are conclusive.
//
// Probe results are cached and persist throughout any process capability changes.
func Supports(mt MacroType) error {
if mt > mt.Max() {
return os.ErrInvalid
}
mc.Lock()
defer mc.Unlock()
err, ok := macros.macroTypes[mt]
if ok {
return err
}

attr, err := createMacroProbeAttr(mt)
if err != nil {
return fmt.Errorf("couldn't create the attributes for the probe: %w", err)
}

fd, err := sys.ProgLoad(attr)

switch {
// EINVAL occurs when attempting to create a program with an unknown type.
// E2BIG occurs when ProgLoadAttr contains non-zero bytes past the end
// of the struct known by the running kernel, meaning the kernel is too old
// to support the given map type.
case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
err = ebpf.ErrNotSupported

// EPERM is kept as-is and is not converted or wrapped.
case errors.Is(err, unix.EPERM):
break

// Wrap unexpected errors.
case err != nil:
err = fmt.Errorf("unexpected error during feature probe: %w", err)

default:
fd.Close()
}

macros.macroTypes[mt] = err

return err
}

func createMacroProbeAttr(mt MacroType) (*sys.ProgLoadAttr, error) {
var (
insns asm.Instructions
label string
)

switch mt {
case LargeInsn:
for i := 0; i < maxInsns; i++ {
insns = append(insns, asm.Mov.Imm(asm.R0, 1))
}
insns = append(insns, asm.Return())
case BoundedLoops:
label = "boundedLoop"
insns = asm.Instructions{
asm.Mov.Imm(asm.R0, 10),
asm.Sub.Imm(asm.R0, 1).Sym(label),
asm.JNE.Imm(asm.R0, 0, label),
asm.Return(),
}
// Manually adjust jump offset.
if err := adjustJumpOffset(insns, -2); err != nil {
return nil, err
}
case V2ISA:
label = "v2isa"
insns = asm.Instructions{
asm.Mov.Imm(asm.R0, 0).Sym(label),
asm.JLT.Imm(asm.R0, 0, label),
asm.Mov.Imm(asm.R0, 1),
asm.Return(),
}
// Manually adjust jump offset.
if err := adjustJumpOffset(insns, 1); err != nil {
return nil, err
}
case V3ISA:
label = "v3isa"
insns = asm.Instructions{
asm.Mov.Imm(asm.R0, 0).Sym(label),
asm.JLT.Imm32(asm.R0, 0, label),
asm.Mov.Imm(asm.R0, 1),
asm.Return(),
}
// Manually adjust jump offset.
if err := adjustJumpOffset(insns, 1); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("feature %d not yet implemented", mt)
}

buf := bytes.NewBuffer(make([]byte, 0, insns.Size()))
if err := insns.Marshal(buf, internal.NativeEndian); err != nil {
return nil, err
}

bytecode := buf.Bytes()
instructions := sys.NewSlicePointer(bytecode)

// 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.
v, err := internal.KernelVersion()
if err != nil {
return nil, fmt.Errorf("detecting kernel version: %w", err)
}

return &sys.ProgLoadAttr{
ProgType: sys.BPF_PROG_TYPE_SOCKET_FILTER,
Insns: instructions,
InsnCnt: uint32(len(bytecode) / asm.InstructionSize),
License: sys.NewStringPointer("GPL"),
KernVersion: v.Kernel(),
}, nil
}

// adjustJumpOffset updates the first jump instruction in insns with the
// given newJumpOffset.
// If no jump instruction is found and error is returned.
func adjustJumpOffset(insns asm.Instructions, newJumpOffset int16) error {
iter := insns.Iterate()
for iter.Next() {
ins := iter.Ins
if ins.Reference == "" {
continue
}
if ins.OpCode.Class().IsJump() {
ins.Offset = newJumpOffset
}
return nil
}
return fmt.Errorf("no jump offset was updated")
}
27 changes: 27 additions & 0 deletions features/macros_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions features/macros_test.go
@@ -0,0 +1,38 @@
package features

import (
"errors"
"math"
"os"
"testing"

"github.com/cilium/ebpf/internal/testutils"
)

func TestFeatureInvalid(t *testing.T) {
if err := Supports(MacroType(math.MaxUint32)); !errors.Is(err, os.ErrInvalid) {
t.Fatalf("Expected os.ErrInvalid but was: %v", err)
}
}

func TestSupports(t *testing.T) {
tests := map[MacroType]string{
LargeInsn: "5.1",
BoundedLoops: "5.2",
V2ISA: "4.13",
V3ISA: "5.0",
}

for macro, minVersion := range tests {
macro := macro
minVersion := minVersion
t.Run(macro.String(), func(t *testing.T) {
testutils.SkipOnOldKernel(t, minVersion, macro.String())

if err := Supports(macro); err != nil {
t.Fatalf("Feature %s isn't supported even though kernel is at least %s: %v",
macro.String(), minVersion, err)
}
})
}
}

0 comments on commit adcf979

Please sign in to comment.