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) } }