Skip to content

Commit

Permalink
features: convert misc probes to FeatureTest
Browse files Browse the repository at this point in the history
  • Loading branch information
lmb committed Dec 6, 2022
1 parent cc8154c commit abd0c06
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 157 deletions.
186 changes: 50 additions & 136 deletions features/misc.go
Original file line number Diff line number Diff line change
@@ -1,165 +1,79 @@
package features

import (
"bytes"
"errors"
"fmt"
"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() {
miscs.miscTypes = make(map[miscType]error)
}

var (
miscs miscCache
)

type miscCache struct {
sync.Mutex
miscTypes map[miscType]error
}

type miscType uint32

const (
// largeInsn support introduced in Linux 5.2
// commit c04c0d2b968ac45d6ef020316808ef6c82325a82
largeInsn miscType = iota
// boundedLoops support introduced in Linux 5.3
// commit 2589726d12a1b12eaaa93c7f1ea64287e383c7a5
boundedLoops
// v2ISA support introduced in Linux 4.14
// commit 92b31a9af73b3a3fc801899335d6c47966351830
v2ISA
// v3ISA support introduced in Linux 5.1
// commit 092ed0968bb648cd18e8a0430cd0a8a71727315c
v3ISA
)

const (
maxInsns = 4096
)

// HaveLargeInstructions probes the running kernel if more than 4096 instructions
// per program are supported.
//
// See the package documentation for the meaning of the error return value.
func HaveLargeInstructions() error {
return probeMisc(largeInsn)
}

// HaveBoundedLoops probes the running kernel if bounded loops are supported.
//
// See the package documentation for the meaning of the error return value.
func HaveBoundedLoops() error {
return probeMisc(boundedLoops)
}

// HaveV2ISA probes the running kernel if instructions of the v2 ISA are supported.
//
// See the package documentation for the meaning of the error return value.
func HaveV2ISA() error {
return probeMisc(v2ISA)
}

// HaveV3ISA probes the running kernel if instructions of the v3 ISA are supported.
// Upstream commit c04c0d2b968a ("bpf: increase complexity limit and maximum program size").
//
// See the package documentation for the meaning of the error return value.
func HaveV3ISA() error {
return probeMisc(v3ISA)
}

// probeMisc checks the kernel for a given supported misc by creating
// a specialized program probe and loading it.
func probeMisc(mt miscType) (err error) {
defer func() {
// This closure modifies a named return variable.
err = wrapProbeErrors(err)
}()

miscs.Lock()
defer miscs.Unlock()
err, ok := miscs.miscTypes[mt]
if ok {
return err
}
var HaveLargeInstructions = internal.NewFeatureTest(">4096 instructions", "5.2", func() error {
const maxInsns = 4096

attr, err := createMiscProbeAttr(mt)
if err != nil {
return fmt.Errorf("couldn't create the attributes for the probe: %w", err)
insns := make(asm.Instructions, maxInsns, maxInsns+1)
for i := range insns {
insns[i] = asm.Mov.Imm(asm.R0, 1)
}
insns = append(insns, asm.Return())

fd, err := sys.ProgLoad(attr)
if err == nil {
fd.Close()
}

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
}
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.SocketFilter,
Instructions: insns,
})
})

miscs.miscTypes[mt] = err

return err
}

func createMiscProbeAttr(mt miscType) (*sys.ProgLoadAttr, error) {
var insns asm.Instructions
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:
insns = asm.Instructions{
// HaveBoundedLoops probes the running kernel if bounded loops are supported.
//
// Upstream commit 2589726d12a1 ("bpf: introduce bounded loops").
//
// See the package documentation for the meaning of the error return value.
var HaveBoundedLoops = internal.NewFeatureTest("bounded loops", "5.3", func() error {
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.SocketFilter,
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 10),
asm.Sub.Imm(asm.R0, 1).WithSymbol("loop"),
asm.JNE.Imm(asm.R0, 0, "loop"),
asm.Return(),
}
case v2ISA:
insns = asm.Instructions{
},
})
})

// HaveV2ISA probes the running kernel if instructions of the v2 ISA are supported.
//
// Upstream commit 92b31a9af73b ("bpf: add BPF_J{LT,LE,SLT,SLE} instructions").
//
// See the package documentation for the meaning of the error return value.
var HaveV2ISA = internal.NewFeatureTest("v2 ISA", "4.14", func() error {
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.SocketFilter,
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.JLT.Imm(asm.R0, 0, "exit"),
asm.Mov.Imm(asm.R0, 1),
asm.Return().WithSymbol("exit"),
}
case v3ISA:
insns = asm.Instructions{
},
})
})

// HaveV3ISA probes the running kernel if instructions of the v3 ISA are supported.
//
// Upstream commit 092ed0968bb6 ("bpf: verifier support JMP32").
//
// See the package documentation for the meaning of the error return value.
var HaveV3ISA = internal.NewFeatureTest("v3 ISA", "5.1", func() error {
return probeProgram(&ebpf.ProgramSpec{
Type: ebpf.SocketFilter,
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.JLT.Imm32(asm.R0, 0, "exit"),
asm.Mov.Imm(asm.R0, 1),
asm.Return().WithSymbol("exit"),
}
default:
return nil, fmt.Errorf("misc probe %d not 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)

return &sys.ProgLoadAttr{
ProgType: sys.BPF_PROG_TYPE_SOCKET_FILTER,
Insns: instructions,
InsnCnt: uint32(len(bytecode) / asm.InstructionSize),
License: sys.NewStringPointer("MIT"),
}, nil
}
},
})
})
33 changes: 12 additions & 21 deletions features/misc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,18 @@ import (
"github.com/cilium/ebpf/internal/testutils"
)

func TestHaveMisc(t *testing.T) {
tests := map[string]struct {
typ miscType
probe func() error
minKernel string
}{
"large instructions": {typ: largeInsn, probe: HaveLargeInstructions, minKernel: "5.2"},
"bounded loops": {typ: boundedLoops, probe: HaveBoundedLoops, minKernel: "5.3"},
"v2 ISA": {typ: v2ISA, probe: HaveV2ISA, minKernel: "4.14"},
"v3 ISA": {typ: v3ISA, probe: HaveV3ISA, minKernel: "5.1"},
}
func TestHaveLargeInstructions(t *testing.T) {
testutils.CheckFeatureTest(t, HaveLargeInstructions)
}

func TestHaveBoundedLoops(t *testing.T) {
testutils.CheckFeatureTest(t, HaveBoundedLoops)
}

for misc, test := range tests {
test := test
t.Run(misc, func(t *testing.T) {
testutils.SkipOnOldKernel(t, test.minKernel, misc)
func TestHaveV2ISA(t *testing.T) {
testutils.CheckFeatureTest(t, HaveV2ISA)
}

if err := test.probe(); err != nil {
t.Fatalf("Feature %s isn't supported even though kernel is at least %s: %v",
misc, test.minKernel, err)
}
})
}
func TestHaveV3ISA(t *testing.T) {
testutils.CheckFeatureTest(t, HaveV3ISA)
}

0 comments on commit abd0c06

Please sign in to comment.