322 lines
8.1 KiB
Go
322 lines
8.1 KiB
Go
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)
|
|
}
|
|
}
|