Skip to content

Commit

Permalink
features: simplify HaveProgramHelper
Browse files Browse the repository at this point in the history
  • Loading branch information
lmb committed Dec 6, 2022
1 parent e3a8cab commit cc8154c
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 195 deletions.
171 changes: 43 additions & 128 deletions features/prog.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package features

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

Expand All @@ -14,81 +12,6 @@ import (
"github.com/cilium/ebpf/internal/unix"
)

func init() {
pc.helpers = make(map[ebpf.ProgramType]map[asm.BuiltinFunc]error)
allocHelperCache()
}

func allocHelperCache() {
for pt := ebpf.UnspecifiedProgram + 1; pt <= pt.Max(); pt++ {
pc.helpers[pt] = make(map[asm.BuiltinFunc]error)
}
}

var (
pc progCache
)

type progCache struct {
helperMu sync.Mutex
helpers map[ebpf.ProgramType]map[asm.BuiltinFunc]error
}

func createProgLoadAttr(pt ebpf.ProgramType, helper asm.BuiltinFunc) (*sys.ProgLoadAttr, error) {
var expectedAttachType ebpf.AttachType
var progFlags uint32

insns := asm.Instructions{
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
}

if helper != asm.FnUnspec {
insns = append(asm.Instructions{helper.Call()}, insns...)
}

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)

// Some programs have expected attach types which are checked during the
// BPF_PROG_LOAD syscall.
switch pt {
case ebpf.CGroupSockAddr:
expectedAttachType = ebpf.AttachCGroupInet4Connect
case ebpf.CGroupSockopt:
expectedAttachType = ebpf.AttachCGroupGetsockopt
case ebpf.SkLookup:
expectedAttachType = ebpf.AttachSkLookup
case ebpf.Syscall:
progFlags = unix.BPF_F_SLEEPABLE
default:
expectedAttachType = ebpf.AttachNone
}

// 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.ProgType(pt),
Insns: instructions,
InsnCnt: uint32(len(bytecode) / asm.InstructionSize),
ProgFlags: progFlags,
ExpectedAttachType: sys.AttachType(expectedAttachType),
License: sys.NewStringPointer("GPL"),
KernVersion: v.Kernel(),
}, nil
}

// HaveProgType probes the running kernel for the availability of the specified program type.
//
// Deprecated: use HaveProgramType() instead.
Expand All @@ -101,21 +24,6 @@ func HaveProgramType(pt ebpf.ProgramType) (err error) {
return haveProgramTypeMatrix.Result(pt)
}

func validateProgramType(pt ebpf.ProgramType) error {
if pt > pt.Max() {
return os.ErrInvalid
}

if progLoadProbeNotImplemented(pt) {
// A probe for a these prog types has BTF requirements we currently cannot meet
// Once we figure out how to add a working probe in this package, we can remove
// this check
return fmt.Errorf("a probe for ProgType %s isn't implemented", pt.String())
}

return nil
}

func probeProgram(spec *ebpf.ProgramSpec) error {
if spec.Instructions == nil {
spec.Instructions = asm.Instructions{
Expand Down Expand Up @@ -231,6 +139,18 @@ func init() {
}
}

type helperKey struct {
typ ebpf.ProgramType
helper asm.BuiltinFunc
}

var helperCache = struct {
sync.Mutex
results map[helperKey]error
}{
results: make(map[helperKey]error),
}

// HaveProgramHelper probes the running kernel for the availability of the specified helper
// function to a specified program type.
// Return values have the following semantics:
Expand All @@ -250,42 +170,49 @@ func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) (err error)
err = wrapProbeErrors(err)
}()

if err := validateProgramType(pt); err != nil {
return err
}

if err := validateProgramHelper(helper); err != nil {
return err
}

return haveProgramHelper(pt, helper)
}

func validateProgramHelper(helper asm.BuiltinFunc) error {
if helper > helper.Max() {
return os.ErrInvalid
}

return nil
return haveProgramHelper(pt, helper)
}

func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
pc.helperMu.Lock()
defer pc.helperMu.Unlock()
if err, ok := pc.helpers[pt][helper]; ok {
helperCache.Lock()
defer helperCache.Unlock()

key := helperKey{pt, helper}
if err, ok := helperCache.results[key]; ok {
return err
}

attr, err := createProgLoadAttr(pt, helper)
if err != nil {
return fmt.Errorf("couldn't create the program load attribute: %w", err)
if err := HaveProgramType(pt); err != nil {
return err
}

fd, err := sys.ProgLoad(attr)
if fd != nil {
fd.Close()
spec := &ebpf.ProgramSpec{
Type: pt,
Instructions: asm.Instructions{
helper.Call(),
asm.LoadImm(asm.R0, 0, asm.DWord),
asm.Return(),
},
License: "GPL",
}

switch pt {
case ebpf.CGroupSockAddr:
spec.AttachType = ebpf.AttachCGroupInet4Connect
case ebpf.CGroupSockopt:
spec.AttachType = ebpf.AttachCGroupGetsockopt
case ebpf.SkLookup:
spec.AttachType = ebpf.AttachSkLookup
case ebpf.Syscall:
spec.Flags = unix.BPF_F_SLEEPABLE
}

err := probeProgram(spec)

switch {
// EACCES occurs when attempting to create a program probe with a helper
// while the register args when calling this helper aren't set up properly.
Expand All @@ -296,23 +223,11 @@ func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
err = nil

// EINVAL occurs when attempting to create a program with an unknown helper.
// E2BIG occurs when BPFProgLoadAttr 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 prog type.
case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
case errors.Is(err, unix.EINVAL):
// TODO: possibly we need to check verifier output here to be sure
err = ebpf.ErrNotSupported
}

pc.helpers[pt][helper] = err

helperCache.results[key] = err
return err
}

func progLoadProbeNotImplemented(pt ebpf.ProgramType) bool {
switch pt {
case ebpf.Tracing, ebpf.Extension, ebpf.LSM:
return true
}
return false
}
61 changes: 3 additions & 58 deletions features/prog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,6 @@ import (
"github.com/cilium/ebpf/internal/testutils"
)

var progTypeMinVersion = map[ebpf.ProgramType]string{
ebpf.SocketFilter: "3.19",
ebpf.Kprobe: "4.1",
ebpf.SchedCLS: "4.1",
ebpf.SchedACT: "4.1",
ebpf.TracePoint: "4.7",
ebpf.XDP: "4.8",
ebpf.PerfEvent: "4.9",
ebpf.CGroupSKB: "4.10",
ebpf.CGroupSock: "4.10",
ebpf.LWTIn: "4.10",
ebpf.LWTOut: "4.10",
ebpf.LWTXmit: "4.10",
ebpf.SockOps: "4.13",
ebpf.SkSKB: "4.14",
ebpf.CGroupDevice: "4.15",
ebpf.SkMsg: "4.17",
ebpf.RawTracepoint: "4.17",
ebpf.CGroupSockAddr: "4.17",
ebpf.LWTSeg6Local: "4.18",
ebpf.LircMode2: "4.18",
ebpf.SkReuseport: "4.19",
ebpf.FlowDissector: "4.20",
ebpf.CGroupSysctl: "5.2",
ebpf.RawTracepointWritable: "5.2",
ebpf.CGroupSockopt: "5.3",
ebpf.Tracing: "5.5",
ebpf.StructOps: "5.6",
ebpf.Extension: "5.6",
ebpf.LSM: "5.7",
ebpf.SkLookup: "5.9",
ebpf.Syscall: "5.14",
}

func TestHaveProgramType(t *testing.T) {
testutils.CheckFeatureMatrix(t, haveProgramTypeMatrix)
}
Expand Down Expand Up @@ -97,26 +63,10 @@ func TestHaveProgramHelper(t *testing.T) {
}

for _, tc := range testCases {
minVersion := progTypeMinVersion[tc.prog]

progVersion, err := internal.NewVersion(minVersion)
if err != nil {
t.Fatalf("Could not read kernel version required for program: %v", err)
}

helperVersion, err := internal.NewVersion(tc.version)
if err != nil {
t.Fatalf("Could not read kernel version required for helper: %v", err)
}

if progVersion.Less(helperVersion) {
minVersion = tc.version
}

t.Run(fmt.Sprintf("%s/%s", tc.prog.String(), tc.helper.String()), func(t *testing.T) {
feature := fmt.Sprintf("helper %s for program type %s", tc.helper.String(), tc.prog.String())

testutils.SkipOnOldKernel(t, minVersion, feature)
testutils.SkipOnOldKernel(t, tc.version, feature)

err := HaveProgramHelper(tc.prog, tc.helper)
if !errors.Is(err, tc.expected) {
Expand All @@ -129,14 +79,9 @@ func TestHaveProgramHelper(t *testing.T) {
}

func TestHaveProgramHelperUnsupported(t *testing.T) {
pt := ebpf.SocketFilter
minVersion := progTypeMinVersion[pt]

feature := fmt.Sprintf("program type %s", pt.String())

testutils.SkipOnOldKernel(t, minVersion, feature)
testutils.SkipIfNotSupported(t, HaveProgramType(ebpf.SocketFilter))

if err := haveProgramHelper(pt, asm.BuiltinFunc(math.MaxInt32)); !errors.Is(err, ebpf.ErrNotSupported) {
if err := haveProgramHelper(ebpf.SocketFilter, asm.BuiltinFunc(math.MaxInt32)); !errors.Is(err, ebpf.ErrNotSupported) {
t.Fatalf("Expected ebpf.ErrNotSupported but was: %v", err)
}
}
6 changes: 0 additions & 6 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,6 @@ func (mt MapType) canStoreProgram() bool {
// ProgramType of the eBPF program
type ProgramType uint32

// Max return the latest supported ProgramType.
func (ProgramType) Max() ProgramType {
return maxProgramType - 1
}

// eBPF program types
const (
UnspecifiedProgram ProgramType = iota
Expand Down Expand Up @@ -156,7 +151,6 @@ const (
LSM
SkLookup
Syscall
maxProgramType
)

// AttachType of the eBPF program, needed to differentiate allowed context accesses in
Expand Down
5 changes: 2 additions & 3 deletions types_string.go

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

0 comments on commit cc8154c

Please sign in to comment.