whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
// Package features allows probing for BPF features available to the calling process.
|
||||
//
|
||||
// In general, the error return values from feature probes in this package
|
||||
// all have the following semantics unless otherwise specified:
|
||||
//
|
||||
// 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 resource
|
||||
// creation may succeed despite an error being returned. For example, some
|
||||
// map and program types cannot reliably be probed and will return an
|
||||
// inconclusive error.
|
||||
//
|
||||
// As a rule, only `nil` and `ebpf.ErrNotSupported` are conclusive.
|
||||
//
|
||||
// Probe results are cached by the library and persist throughout any changes
|
||||
// to the process' environment, like capability changes.
|
||||
package features
|
||||
@@ -0,0 +1,321 @@
|
||||
package features
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/internal"
|
||||
"github.com/cilium/ebpf/internal/sys"
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
// HaveMapType probes the running kernel for the availability of the specified map type.
|
||||
//
|
||||
// See the package documentation for the meaning of the error return value.
|
||||
func HaveMapType(mt ebpf.MapType) error {
|
||||
return haveMapTypeMatrix.Result(mt)
|
||||
}
|
||||
|
||||
func probeCgroupStorageMap(mt sys.MapType) error {
|
||||
// keySize needs to be sizeof(struct{u32 + u64}) = 12 (+ padding = 16)
|
||||
// by using unsafe.Sizeof(int) we are making sure that this works on 32bit and 64bit archs
|
||||
return createMap(&sys.MapCreateAttr{
|
||||
MapType: mt,
|
||||
ValueSize: 4,
|
||||
KeySize: uint32(8 + unsafe.Sizeof(int(0))),
|
||||
MaxEntries: 0,
|
||||
})
|
||||
}
|
||||
|
||||
func probeStorageMap(mt sys.MapType) error {
|
||||
// maxEntries needs to be 0
|
||||
// BPF_F_NO_PREALLOC needs to be set
|
||||
// btf* fields need to be set
|
||||
// see alloc_check for local_storage map types
|
||||
err := createMap(&sys.MapCreateAttr{
|
||||
MapType: mt,
|
||||
KeySize: 4,
|
||||
ValueSize: 4,
|
||||
MaxEntries: 0,
|
||||
MapFlags: unix.BPF_F_NO_PREALLOC,
|
||||
BtfKeyTypeId: 1,
|
||||
BtfValueTypeId: 1,
|
||||
BtfFd: ^uint32(0),
|
||||
})
|
||||
if errors.Is(err, unix.EBADF) {
|
||||
// Triggered by BtfFd.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func probeNestedMap(mt sys.MapType) error {
|
||||
// assign invalid innerMapFd to pass validation check
|
||||
// will return EBADF
|
||||
err := probeMap(&sys.MapCreateAttr{
|
||||
MapType: mt,
|
||||
InnerMapFd: ^uint32(0),
|
||||
})
|
||||
if errors.Is(err, unix.EBADF) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func probeMap(attr *sys.MapCreateAttr) error {
|
||||
if attr.KeySize == 0 {
|
||||
attr.KeySize = 4
|
||||
}
|
||||
if attr.ValueSize == 0 {
|
||||
attr.ValueSize = 4
|
||||
}
|
||||
attr.MaxEntries = 1
|
||||
return createMap(attr)
|
||||
}
|
||||
|
||||
func createMap(attr *sys.MapCreateAttr) error {
|
||||
fd, err := sys.MapCreate(attr)
|
||||
if err == nil {
|
||||
fd.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
// EINVAL occurs when attempting to create a map with an unknown type.
|
||||
// E2BIG occurs when MapCreateAttr 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):
|
||||
return ebpf.ErrNotSupported
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var haveMapTypeMatrix = internal.FeatureMatrix[ebpf.MapType]{
|
||||
ebpf.Hash: {Version: "3.19"},
|
||||
ebpf.Array: {Version: "3.19"},
|
||||
ebpf.ProgramArray: {Version: "4.2"},
|
||||
ebpf.PerfEventArray: {Version: "4.3"},
|
||||
ebpf.PerCPUHash: {Version: "4.6"},
|
||||
ebpf.PerCPUArray: {Version: "4.6"},
|
||||
ebpf.StackTrace: {
|
||||
Version: "4.6",
|
||||
Fn: func() error {
|
||||
return probeMap(&sys.MapCreateAttr{
|
||||
MapType: sys.BPF_MAP_TYPE_STACK_TRACE,
|
||||
ValueSize: 8, // sizeof(uint64)
|
||||
})
|
||||
},
|
||||
},
|
||||
ebpf.CGroupArray: {Version: "4.8"},
|
||||
ebpf.LRUHash: {Version: "4.10"},
|
||||
ebpf.LRUCPUHash: {Version: "4.10"},
|
||||
ebpf.LPMTrie: {
|
||||
Version: "4.11",
|
||||
Fn: func() error {
|
||||
// keySize and valueSize need to be sizeof(struct{u32 + u8}) + 1 + padding = 8
|
||||
// BPF_F_NO_PREALLOC needs to be set
|
||||
return probeMap(&sys.MapCreateAttr{
|
||||
MapType: sys.BPF_MAP_TYPE_LPM_TRIE,
|
||||
KeySize: 8,
|
||||
ValueSize: 8,
|
||||
MapFlags: unix.BPF_F_NO_PREALLOC,
|
||||
})
|
||||
},
|
||||
},
|
||||
ebpf.ArrayOfMaps: {
|
||||
Version: "4.12",
|
||||
Fn: func() error { return probeNestedMap(sys.BPF_MAP_TYPE_ARRAY_OF_MAPS) },
|
||||
},
|
||||
ebpf.HashOfMaps: {
|
||||
Version: "4.12",
|
||||
Fn: func() error { return probeNestedMap(sys.BPF_MAP_TYPE_HASH_OF_MAPS) },
|
||||
},
|
||||
ebpf.DevMap: {Version: "4.14"},
|
||||
ebpf.SockMap: {Version: "4.14"},
|
||||
ebpf.CPUMap: {Version: "4.15"},
|
||||
ebpf.XSKMap: {Version: "4.18"},
|
||||
ebpf.SockHash: {Version: "4.18"},
|
||||
ebpf.CGroupStorage: {
|
||||
Version: "4.19",
|
||||
Fn: func() error { return probeCgroupStorageMap(sys.BPF_MAP_TYPE_CGROUP_STORAGE) },
|
||||
},
|
||||
ebpf.ReusePortSockArray: {Version: "4.19"},
|
||||
ebpf.PerCPUCGroupStorage: {
|
||||
Version: "4.20",
|
||||
Fn: func() error { return probeCgroupStorageMap(sys.BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE) },
|
||||
},
|
||||
ebpf.Queue: {
|
||||
Version: "4.20",
|
||||
Fn: func() error {
|
||||
return createMap(&sys.MapCreateAttr{
|
||||
MapType: sys.BPF_MAP_TYPE_QUEUE,
|
||||
KeySize: 0,
|
||||
ValueSize: 4,
|
||||
MaxEntries: 1,
|
||||
})
|
||||
},
|
||||
},
|
||||
ebpf.Stack: {
|
||||
Version: "4.20",
|
||||
Fn: func() error {
|
||||
return createMap(&sys.MapCreateAttr{
|
||||
MapType: sys.BPF_MAP_TYPE_STACK,
|
||||
KeySize: 0,
|
||||
ValueSize: 4,
|
||||
MaxEntries: 1,
|
||||
})
|
||||
},
|
||||
},
|
||||
ebpf.SkStorage: {
|
||||
Version: "5.2",
|
||||
Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_SK_STORAGE) },
|
||||
},
|
||||
ebpf.DevMapHash: {Version: "5.4"},
|
||||
ebpf.StructOpsMap: {
|
||||
Version: "5.6",
|
||||
Fn: func() error {
|
||||
// StructOps requires setting a vmlinux type id, but id 1 will always
|
||||
// resolve to some type of integer. This will cause ENOTSUPP.
|
||||
err := probeMap(&sys.MapCreateAttr{
|
||||
MapType: sys.BPF_MAP_TYPE_STRUCT_OPS,
|
||||
BtfVmlinuxValueTypeId: 1,
|
||||
})
|
||||
if errors.Is(err, sys.ENOTSUPP) {
|
||||
// ENOTSUPP means the map type is at least known to the kernel.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
},
|
||||
},
|
||||
ebpf.RingBuf: {
|
||||
Version: "5.8",
|
||||
Fn: func() error {
|
||||
// keySize and valueSize need to be 0
|
||||
// maxEntries needs to be power of 2 and PAGE_ALIGNED
|
||||
return createMap(&sys.MapCreateAttr{
|
||||
MapType: sys.BPF_MAP_TYPE_RINGBUF,
|
||||
KeySize: 0,
|
||||
ValueSize: 0,
|
||||
MaxEntries: uint32(os.Getpagesize()),
|
||||
})
|
||||
},
|
||||
},
|
||||
ebpf.InodeStorage: {
|
||||
Version: "5.10",
|
||||
Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_INODE_STORAGE) },
|
||||
},
|
||||
ebpf.TaskStorage: {
|
||||
Version: "5.11",
|
||||
Fn: func() error { return probeStorageMap(sys.BPF_MAP_TYPE_TASK_STORAGE) },
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
for mt, ft := range haveMapTypeMatrix {
|
||||
ft.Name = mt.String()
|
||||
if ft.Fn == nil {
|
||||
// Avoid referring to the loop variable in the closure.
|
||||
mt := sys.MapType(mt)
|
||||
ft.Fn = func() error { return probeMap(&sys.MapCreateAttr{MapType: mt}) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MapFlags document which flags may be feature probed.
|
||||
type MapFlags = sys.MapFlags
|
||||
|
||||
// Flags which may be feature probed.
|
||||
const (
|
||||
BPF_F_NO_PREALLOC = sys.BPF_F_NO_PREALLOC
|
||||
BPF_F_RDONLY_PROG = sys.BPF_F_RDONLY_PROG
|
||||
BPF_F_WRONLY_PROG = sys.BPF_F_WRONLY_PROG
|
||||
BPF_F_MMAPABLE = sys.BPF_F_MMAPABLE
|
||||
BPF_F_INNER_MAP = sys.BPF_F_INNER_MAP
|
||||
)
|
||||
|
||||
// HaveMapFlag probes the running kernel for the availability of the specified map flag.
|
||||
//
|
||||
// Returns an error if flag is not one of the flags declared in this package.
|
||||
// See the package documentation for the meaning of the error return value.
|
||||
func HaveMapFlag(flag MapFlags) (err error) {
|
||||
return haveMapFlagsMatrix.Result(flag)
|
||||
}
|
||||
|
||||
func probeMapFlag(attr *sys.MapCreateAttr) error {
|
||||
// For now, we do not check if the map type is supported because we only support
|
||||
// probing for flags defined on arrays and hashs that are always supported.
|
||||
// In the future, if we allow probing on flags defined on newer types, checking for map type
|
||||
// support will be required.
|
||||
if attr.MapType == sys.BPF_MAP_TYPE_UNSPEC {
|
||||
attr.MapType = sys.BPF_MAP_TYPE_ARRAY
|
||||
}
|
||||
|
||||
attr.KeySize = 4
|
||||
attr.ValueSize = 4
|
||||
attr.MaxEntries = 1
|
||||
|
||||
fd, err := sys.MapCreate(attr)
|
||||
if err == nil {
|
||||
fd.Close()
|
||||
} else if errors.Is(err, unix.EINVAL) {
|
||||
// EINVAL occurs when attempting to create a map with an unknown type or an unknown flag.
|
||||
err = ebpf.ErrNotSupported
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var haveMapFlagsMatrix = internal.FeatureMatrix[MapFlags]{
|
||||
BPF_F_NO_PREALLOC: {
|
||||
Version: "4.6",
|
||||
Fn: func() error {
|
||||
return probeMapFlag(&sys.MapCreateAttr{
|
||||
MapType: sys.BPF_MAP_TYPE_HASH,
|
||||
MapFlags: BPF_F_NO_PREALLOC,
|
||||
})
|
||||
},
|
||||
},
|
||||
BPF_F_RDONLY_PROG: {
|
||||
Version: "5.2",
|
||||
Fn: func() error {
|
||||
return probeMapFlag(&sys.MapCreateAttr{
|
||||
MapFlags: BPF_F_RDONLY_PROG,
|
||||
})
|
||||
},
|
||||
},
|
||||
BPF_F_WRONLY_PROG: {
|
||||
Version: "5.2",
|
||||
Fn: func() error {
|
||||
return probeMapFlag(&sys.MapCreateAttr{
|
||||
MapFlags: BPF_F_WRONLY_PROG,
|
||||
})
|
||||
},
|
||||
},
|
||||
BPF_F_MMAPABLE: {
|
||||
Version: "5.5",
|
||||
Fn: func() error {
|
||||
return probeMapFlag(&sys.MapCreateAttr{
|
||||
MapFlags: BPF_F_MMAPABLE,
|
||||
})
|
||||
},
|
||||
},
|
||||
BPF_F_INNER_MAP: {
|
||||
Version: "5.10",
|
||||
Fn: func() error {
|
||||
return probeMapFlag(&sys.MapCreateAttr{
|
||||
MapFlags: BPF_F_INNER_MAP,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
for mf, ft := range haveMapFlagsMatrix {
|
||||
ft.Name = fmt.Sprint(mf)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package features
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/internal"
|
||||
"github.com/cilium/ebpf/internal/testutils"
|
||||
)
|
||||
|
||||
func TestHaveMapType(t *testing.T) {
|
||||
testutils.CheckFeatureMatrix(t, haveMapTypeMatrix)
|
||||
}
|
||||
|
||||
func TestHaveMapFlag(t *testing.T) {
|
||||
testutils.CheckFeatureMatrix(t, haveMapFlagsMatrix)
|
||||
}
|
||||
|
||||
func TestHaveMapTypeInvalid(t *testing.T) {
|
||||
if err := HaveMapType(ebpf.MapType(math.MaxUint32)); err == nil {
|
||||
t.Fatal("Expected an error")
|
||||
} else if errors.Is(err, internal.ErrNotSupported) {
|
||||
t.Fatal("Got ErrNotSupported:", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package features
|
||||
|
||||
import (
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/asm"
|
||||
"github.com/cilium/ebpf/internal"
|
||||
)
|
||||
|
||||
// HaveLargeInstructions probes the running kernel if more than 4096 instructions
|
||||
// per program 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.
|
||||
var HaveLargeInstructions = internal.NewFeatureTest(">4096 instructions", "5.2", func() error {
|
||||
const maxInsns = 4096
|
||||
|
||||
insns := make(asm.Instructions, maxInsns, maxInsns+1)
|
||||
for i := range insns {
|
||||
insns[i] = asm.Mov.Imm(asm.R0, 1)
|
||||
}
|
||||
insns = append(insns, asm.Return())
|
||||
|
||||
return probeProgram(&ebpf.ProgramSpec{
|
||||
Type: ebpf.SocketFilter,
|
||||
Instructions: insns,
|
||||
})
|
||||
})
|
||||
|
||||
// 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(),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// 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"),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// 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"),
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
package features
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cilium/ebpf/internal/testutils"
|
||||
)
|
||||
|
||||
func TestHaveLargeInstructions(t *testing.T) {
|
||||
testutils.CheckFeatureTest(t, HaveLargeInstructions)
|
||||
}
|
||||
|
||||
func TestHaveBoundedLoops(t *testing.T) {
|
||||
testutils.CheckFeatureTest(t, HaveBoundedLoops)
|
||||
}
|
||||
|
||||
func TestHaveV2ISA(t *testing.T) {
|
||||
testutils.CheckFeatureTest(t, HaveV2ISA)
|
||||
}
|
||||
|
||||
func TestHaveV3ISA(t *testing.T) {
|
||||
testutils.CheckFeatureTest(t, HaveV3ISA)
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package features
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/asm"
|
||||
"github.com/cilium/ebpf/btf"
|
||||
"github.com/cilium/ebpf/internal"
|
||||
"github.com/cilium/ebpf/internal/sys"
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
// HaveProgType probes the running kernel for the availability of the specified program type.
|
||||
//
|
||||
// Deprecated: use HaveProgramType() instead.
|
||||
var HaveProgType = HaveProgramType
|
||||
|
||||
// HaveProgramType probes the running kernel for the availability of the specified program type.
|
||||
//
|
||||
// See the package documentation for the meaning of the error return value.
|
||||
func HaveProgramType(pt ebpf.ProgramType) (err error) {
|
||||
return haveProgramTypeMatrix.Result(pt)
|
||||
}
|
||||
|
||||
func probeProgram(spec *ebpf.ProgramSpec) error {
|
||||
if spec.Instructions == nil {
|
||||
spec.Instructions = asm.Instructions{
|
||||
asm.LoadImm(asm.R0, 0, asm.DWord),
|
||||
asm.Return(),
|
||||
}
|
||||
}
|
||||
prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{
|
||||
LogDisabled: true,
|
||||
})
|
||||
if err == nil {
|
||||
prog.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 prog type.
|
||||
case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
|
||||
err = ebpf.ErrNotSupported
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var haveProgramTypeMatrix = internal.FeatureMatrix[ebpf.ProgramType]{
|
||||
ebpf.SocketFilter: {Version: "3.19"},
|
||||
ebpf.Kprobe: {Version: "4.1"},
|
||||
ebpf.SchedCLS: {Version: "4.1"},
|
||||
ebpf.SchedACT: {Version: "4.1"},
|
||||
ebpf.TracePoint: {Version: "4.7"},
|
||||
ebpf.XDP: {Version: "4.8"},
|
||||
ebpf.PerfEvent: {Version: "4.9"},
|
||||
ebpf.CGroupSKB: {Version: "4.10"},
|
||||
ebpf.CGroupSock: {Version: "4.10"},
|
||||
ebpf.LWTIn: {Version: "4.10"},
|
||||
ebpf.LWTOut: {Version: "4.10"},
|
||||
ebpf.LWTXmit: {Version: "4.10"},
|
||||
ebpf.SockOps: {Version: "4.13"},
|
||||
ebpf.SkSKB: {Version: "4.14"},
|
||||
ebpf.CGroupDevice: {Version: "4.15"},
|
||||
ebpf.SkMsg: {Version: "4.17"},
|
||||
ebpf.RawTracepoint: {Version: "4.17"},
|
||||
ebpf.CGroupSockAddr: {
|
||||
Version: "4.17",
|
||||
Fn: func() error {
|
||||
return probeProgram(&ebpf.ProgramSpec{
|
||||
Type: ebpf.CGroupSockAddr,
|
||||
AttachType: ebpf.AttachCGroupInet4Connect,
|
||||
})
|
||||
},
|
||||
},
|
||||
ebpf.LWTSeg6Local: {Version: "4.18"},
|
||||
ebpf.LircMode2: {Version: "4.18"},
|
||||
ebpf.SkReuseport: {Version: "4.19"},
|
||||
ebpf.FlowDissector: {Version: "4.20"},
|
||||
ebpf.CGroupSysctl: {Version: "5.2"},
|
||||
ebpf.RawTracepointWritable: {Version: "5.2"},
|
||||
ebpf.CGroupSockopt: {
|
||||
Version: "5.3",
|
||||
Fn: func() error {
|
||||
return probeProgram(&ebpf.ProgramSpec{
|
||||
Type: ebpf.CGroupSockopt,
|
||||
AttachType: ebpf.AttachCGroupGetsockopt,
|
||||
})
|
||||
},
|
||||
},
|
||||
ebpf.Tracing: {
|
||||
Version: "5.5",
|
||||
Fn: func() error {
|
||||
return probeProgram(&ebpf.ProgramSpec{
|
||||
Type: ebpf.Tracing,
|
||||
AttachType: ebpf.AttachTraceFEntry,
|
||||
AttachTo: "bpf_init",
|
||||
})
|
||||
},
|
||||
},
|
||||
ebpf.StructOps: {
|
||||
Version: "5.6",
|
||||
Fn: func() error {
|
||||
err := probeProgram(&ebpf.ProgramSpec{
|
||||
Type: ebpf.StructOps,
|
||||
License: "GPL",
|
||||
})
|
||||
if errors.Is(err, sys.ENOTSUPP) {
|
||||
// ENOTSUPP means the program type is at least known to the kernel.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
},
|
||||
},
|
||||
ebpf.Extension: {
|
||||
Version: "5.6",
|
||||
Fn: func() error {
|
||||
// create btf.Func to add to first ins of target and extension so both progs are btf powered
|
||||
btfFn := btf.Func{
|
||||
Name: "a",
|
||||
Type: &btf.FuncProto{
|
||||
Return: &btf.Int{},
|
||||
},
|
||||
Linkage: btf.GlobalFunc,
|
||||
}
|
||||
insns := asm.Instructions{
|
||||
btf.WithFuncMetadata(asm.Mov.Imm(asm.R0, 0), &btfFn),
|
||||
asm.Return(),
|
||||
}
|
||||
|
||||
// create target prog
|
||||
prog, err := ebpf.NewProgramWithOptions(
|
||||
&ebpf.ProgramSpec{
|
||||
Type: ebpf.XDP,
|
||||
Instructions: insns,
|
||||
},
|
||||
ebpf.ProgramOptions{
|
||||
LogDisabled: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer prog.Close()
|
||||
|
||||
// probe for Extension prog with target
|
||||
return probeProgram(&ebpf.ProgramSpec{
|
||||
Type: ebpf.Extension,
|
||||
Instructions: insns,
|
||||
AttachTarget: prog,
|
||||
AttachTo: btfFn.Name,
|
||||
})
|
||||
},
|
||||
},
|
||||
ebpf.LSM: {
|
||||
Version: "5.7",
|
||||
Fn: func() error {
|
||||
return probeProgram(&ebpf.ProgramSpec{
|
||||
Type: ebpf.LSM,
|
||||
AttachType: ebpf.AttachLSMMac,
|
||||
AttachTo: "file_mprotect",
|
||||
License: "GPL",
|
||||
})
|
||||
},
|
||||
},
|
||||
ebpf.SkLookup: {
|
||||
Version: "5.9",
|
||||
Fn: func() error {
|
||||
return probeProgram(&ebpf.ProgramSpec{
|
||||
Type: ebpf.SkLookup,
|
||||
AttachType: ebpf.AttachSkLookup,
|
||||
})
|
||||
},
|
||||
},
|
||||
ebpf.Syscall: {
|
||||
Version: "5.14",
|
||||
Fn: func() error {
|
||||
return probeProgram(&ebpf.ProgramSpec{
|
||||
Type: ebpf.Syscall,
|
||||
Flags: unix.BPF_F_SLEEPABLE,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
for key, ft := range haveProgramTypeMatrix {
|
||||
ft.Name = key.String()
|
||||
if ft.Fn == nil {
|
||||
key := key // avoid the dreaded loop variable problem
|
||||
ft.Fn = func() error { return probeProgram(&ebpf.ProgramSpec{Type: key}) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type helperKey struct {
|
||||
typ ebpf.ProgramType
|
||||
helper asm.BuiltinFunc
|
||||
}
|
||||
|
||||
var helperCache = internal.NewFeatureCache(func(key helperKey) *internal.FeatureTest {
|
||||
return &internal.FeatureTest{
|
||||
Name: fmt.Sprintf("%s for program type %s", key.helper, key.typ),
|
||||
Fn: func() error {
|
||||
return haveProgramHelper(key.typ, key.helper)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// HaveProgramHelper probes the running kernel for the availability of the specified helper
|
||||
// function to a specified program type.
|
||||
// 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.
|
||||
// Only `nil` and `ebpf.ErrNotSupported` are conclusive.
|
||||
//
|
||||
// Probe results are cached and persist throughout any process capability changes.
|
||||
func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
|
||||
if helper > helper.Max() {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
return helperCache.Result(helperKey{pt, helper})
|
||||
}
|
||||
|
||||
func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
|
||||
if ok := helperProbeNotImplemented(pt); ok {
|
||||
return fmt.Errorf("no feature probe for %v/%v", pt, helper)
|
||||
}
|
||||
|
||||
if err := HaveProgramType(pt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{
|
||||
LogDisabled: true,
|
||||
})
|
||||
if err == nil {
|
||||
prog.Close()
|
||||
}
|
||||
|
||||
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.
|
||||
// We interpret this as the helper being available, because the verifier
|
||||
// returns EINVAL if the helper is not supported by the running kernel.
|
||||
case errors.Is(err, unix.EACCES):
|
||||
// TODO: possibly we need to check verifier output here to be sure
|
||||
err = nil
|
||||
|
||||
// EINVAL occurs when attempting to create a program with an unknown helper.
|
||||
case errors.Is(err, unix.EINVAL):
|
||||
// TODO: possibly we need to check verifier output here to be sure
|
||||
err = ebpf.ErrNotSupported
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func helperProbeNotImplemented(pt ebpf.ProgramType) bool {
|
||||
switch pt {
|
||||
case ebpf.Extension, ebpf.LSM, ebpf.StructOps, ebpf.Tracing:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package features
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/asm"
|
||||
"github.com/cilium/ebpf/internal"
|
||||
"github.com/cilium/ebpf/internal/testutils"
|
||||
"github.com/cilium/ebpf/internal/testutils/fdtrace"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
fdtrace.TestMain(m)
|
||||
}
|
||||
|
||||
func TestHaveProgramType(t *testing.T) {
|
||||
testutils.CheckFeatureMatrix(t, haveProgramTypeMatrix)
|
||||
}
|
||||
|
||||
func TestHaveProgramTypeInvalid(t *testing.T) {
|
||||
if err := HaveProgramType(ebpf.ProgramType(math.MaxUint32)); err == nil {
|
||||
t.Fatal("Expected an error")
|
||||
} else if errors.Is(err, internal.ErrNotSupported) {
|
||||
t.Fatal("Got ErrNotSupported:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHaveProgramHelper(t *testing.T) {
|
||||
type testCase struct {
|
||||
prog ebpf.ProgramType
|
||||
helper asm.BuiltinFunc
|
||||
expected error
|
||||
version string
|
||||
}
|
||||
|
||||
// Referencing linux kernel commits to track the kernel version required to pass these test cases.
|
||||
// They cases are derived from libbpf's selftests and helper/prog combinations that are
|
||||
// probed for in cilium/cilium.
|
||||
testCases := []testCase{
|
||||
{ebpf.Kprobe, asm.FnMapLookupElem, nil, "3.19"}, // d0003ec01c66
|
||||
{ebpf.SocketFilter, asm.FnKtimeGetCoarseNs, nil, "5.11"}, // d05512618056
|
||||
{ebpf.SchedCLS, asm.FnSkbVlanPush, nil, "4.3"}, // 4e10df9a60d9
|
||||
{ebpf.Kprobe, asm.FnSkbVlanPush, ebpf.ErrNotSupported, "4.3"}, // 4e10df9a60d9
|
||||
{ebpf.Kprobe, asm.FnSysBpf, ebpf.ErrNotSupported, "5.14"}, // 79a7f8bdb159
|
||||
{ebpf.Syscall, asm.FnSysBpf, nil, "5.14"}, // 79a7f8bdb159
|
||||
{ebpf.XDP, asm.FnJiffies64, nil, "5.5"}, // 5576b991e9c1
|
||||
{ebpf.XDP, asm.FnKtimeGetBootNs, nil, "5.7"}, // 71d19214776e
|
||||
{ebpf.SchedCLS, asm.FnSkbChangeHead, nil, "5.8"}, // 6f3f65d80dac
|
||||
{ebpf.SchedCLS, asm.FnRedirectNeigh, nil, "5.10"}, // b4ab31414970
|
||||
{ebpf.SchedCLS, asm.FnSkbEcnSetCe, nil, "5.1"}, // f7c917ba11a6
|
||||
{ebpf.SchedACT, asm.FnSkAssign, nil, "5.6"}, // cf7fbe660f2d
|
||||
{ebpf.SchedACT, asm.FnFibLookup, nil, "4.18"}, // 87f5fc7e48dd
|
||||
{ebpf.Kprobe, asm.FnFibLookup, ebpf.ErrNotSupported, "4.18"}, // 87f5fc7e48dd
|
||||
{ebpf.CGroupSockAddr, asm.FnGetsockopt, nil, "5.8"}, // beecf11bc218
|
||||
{ebpf.CGroupSockAddr, asm.FnSkLookupTcp, nil, "4.20"}, // 6acc9b432e67
|
||||
{ebpf.CGroupSockAddr, asm.FnGetNetnsCookie, nil, "5.7"}, // f318903c0bf4
|
||||
{ebpf.CGroupSock, asm.FnGetNetnsCookie, nil, "5.7"}, // f318903c0bf4
|
||||
{ebpf.Kprobe, asm.FnKtimeGetCoarseNs, ebpf.ErrNotSupported, "5.16"}, // 5e0bc3082e2e
|
||||
{ebpf.CGroupSockAddr, asm.FnGetCgroupClassid, nil, "5.7"}, // 5a52ae4e32a6
|
||||
{ebpf.Kprobe, asm.FnGetBranchSnapshot, nil, "5.16"}, // 856c02dbce4f
|
||||
{ebpf.SchedCLS, asm.FnSkbSetTstamp, nil, "5.18"}, // 9bb984f28d5b
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
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, tc.version, feature)
|
||||
|
||||
err := HaveProgramHelper(tc.prog, tc.helper)
|
||||
if !errors.Is(err, tc.expected) {
|
||||
t.Fatalf("%s/%s: %v", tc.prog.String(), tc.helper.String(), err)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperProbeNotImplemented(t *testing.T) {
|
||||
// Currently we don't support probing helpers for Tracing, Extension, LSM and StructOps programs.
|
||||
// For each of those test the availability of the FnMapLookupElem helper and expect it to fail.
|
||||
for _, pt := range []ebpf.ProgramType{ebpf.Tracing, ebpf.Extension, ebpf.LSM, ebpf.StructOps} {
|
||||
t.Run(pt.String(), func(t *testing.T) {
|
||||
if err := HaveProgramHelper(pt, asm.FnMapLookupElem); err == nil {
|
||||
t.Fatal("Expected an error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package features
|
||||
|
||||
import "github.com/cilium/ebpf/internal"
|
||||
|
||||
// LinuxVersionCode returns the version of the currently running kernel
|
||||
// as defined in the LINUX_VERSION_CODE compile-time macro. It is represented
|
||||
// in the format described by the KERNEL_VERSION macro from linux/version.h.
|
||||
//
|
||||
// Do not use the version to make assumptions about the presence of certain
|
||||
// kernel features, always prefer feature probes in this package. Some
|
||||
// distributions backport or disable eBPF features.
|
||||
func LinuxVersionCode() (uint32, error) {
|
||||
v, err := internal.KernelVersion()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return v.Kernel(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user