| package ebpf |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| "os" |
| "runtime" |
| |
| "github.com/cilium/ebpf/asm" |
| "github.com/cilium/ebpf/internal" |
| "github.com/cilium/ebpf/internal/sys" |
| "github.com/cilium/ebpf/internal/tracefs" |
| "github.com/cilium/ebpf/internal/unix" |
| ) |
| |
| var ( |
| // pre-allocating these here since they may |
| // get called in hot code paths and cause |
| // unnecessary memory allocations |
| sysErrKeyNotExist = sys.Error(ErrKeyNotExist, unix.ENOENT) |
| sysErrKeyExist = sys.Error(ErrKeyExist, unix.EEXIST) |
| sysErrNotSupported = sys.Error(ErrNotSupported, sys.ENOTSUPP) |
| ) |
| |
| // invalidBPFObjNameChar returns true if char may not appear in |
| // a BPF object name. |
| func invalidBPFObjNameChar(char rune) bool { |
| dotAllowed := objNameAllowsDot() == nil |
| |
| switch { |
| case char >= 'A' && char <= 'Z': |
| return false |
| case char >= 'a' && char <= 'z': |
| return false |
| case char >= '0' && char <= '9': |
| return false |
| case dotAllowed && char == '.': |
| return false |
| case char == '_': |
| return false |
| default: |
| return true |
| } |
| } |
| |
| func progLoad(insns asm.Instructions, typ ProgramType, license string) (*sys.FD, error) { |
| buf := bytes.NewBuffer(make([]byte, 0, insns.Size())) |
| if err := insns.Marshal(buf, internal.NativeEndian); err != nil { |
| return nil, err |
| } |
| bytecode := buf.Bytes() |
| |
| return sys.ProgLoad(&sys.ProgLoadAttr{ |
| ProgType: sys.ProgType(typ), |
| License: sys.NewStringPointer(license), |
| Insns: sys.NewSlicePointer(bytecode), |
| InsnCnt: uint32(len(bytecode) / asm.InstructionSize), |
| }) |
| } |
| |
| var haveNestedMaps = internal.NewFeatureTest("nested maps", "4.12", func() error { |
| _, err := sys.MapCreate(&sys.MapCreateAttr{ |
| MapType: sys.MapType(ArrayOfMaps), |
| KeySize: 4, |
| ValueSize: 4, |
| MaxEntries: 1, |
| // Invalid file descriptor. |
| InnerMapFd: ^uint32(0), |
| }) |
| if errors.Is(err, unix.EINVAL) { |
| return internal.ErrNotSupported |
| } |
| if errors.Is(err, unix.EBADF) { |
| return nil |
| } |
| return err |
| }) |
| |
| var haveMapMutabilityModifiers = internal.NewFeatureTest("read- and write-only maps", "5.2", func() error { |
| // This checks BPF_F_RDONLY_PROG and BPF_F_WRONLY_PROG. Since |
| // BPF_MAP_FREEZE appeared in 5.2 as well we don't do a separate check. |
| m, err := sys.MapCreate(&sys.MapCreateAttr{ |
| MapType: sys.MapType(Array), |
| KeySize: 4, |
| ValueSize: 4, |
| MaxEntries: 1, |
| MapFlags: unix.BPF_F_RDONLY_PROG, |
| }) |
| if err != nil { |
| return internal.ErrNotSupported |
| } |
| _ = m.Close() |
| return nil |
| }) |
| |
| var haveMmapableMaps = internal.NewFeatureTest("mmapable maps", "5.5", func() error { |
| // This checks BPF_F_MMAPABLE, which appeared in 5.5 for array maps. |
| m, err := sys.MapCreate(&sys.MapCreateAttr{ |
| MapType: sys.MapType(Array), |
| KeySize: 4, |
| ValueSize: 4, |
| MaxEntries: 1, |
| MapFlags: unix.BPF_F_MMAPABLE, |
| }) |
| if err != nil { |
| return internal.ErrNotSupported |
| } |
| _ = m.Close() |
| return nil |
| }) |
| |
| var haveInnerMaps = internal.NewFeatureTest("inner maps", "5.10", func() error { |
| // This checks BPF_F_INNER_MAP, which appeared in 5.10. |
| m, err := sys.MapCreate(&sys.MapCreateAttr{ |
| MapType: sys.MapType(Array), |
| KeySize: 4, |
| ValueSize: 4, |
| MaxEntries: 1, |
| MapFlags: unix.BPF_F_INNER_MAP, |
| }) |
| if err != nil { |
| return internal.ErrNotSupported |
| } |
| _ = m.Close() |
| return nil |
| }) |
| |
| var haveNoPreallocMaps = internal.NewFeatureTest("prealloc maps", "4.6", func() error { |
| // This checks BPF_F_NO_PREALLOC, which appeared in 4.6. |
| m, err := sys.MapCreate(&sys.MapCreateAttr{ |
| MapType: sys.MapType(Hash), |
| KeySize: 4, |
| ValueSize: 4, |
| MaxEntries: 1, |
| MapFlags: unix.BPF_F_NO_PREALLOC, |
| }) |
| if err != nil { |
| return internal.ErrNotSupported |
| } |
| _ = m.Close() |
| return nil |
| }) |
| |
| func wrapMapError(err error) error { |
| if err == nil { |
| return nil |
| } |
| |
| if errors.Is(err, unix.ENOENT) { |
| return sysErrKeyNotExist |
| } |
| |
| if errors.Is(err, unix.EEXIST) { |
| return sysErrKeyExist |
| } |
| |
| if errors.Is(err, sys.ENOTSUPP) { |
| return sysErrNotSupported |
| } |
| |
| if errors.Is(err, unix.E2BIG) { |
| return fmt.Errorf("key too big for map: %w", err) |
| } |
| |
| return err |
| } |
| |
| var haveObjName = internal.NewFeatureTest("object names", "4.15", func() error { |
| attr := sys.MapCreateAttr{ |
| MapType: sys.MapType(Array), |
| KeySize: 4, |
| ValueSize: 4, |
| MaxEntries: 1, |
| MapName: sys.NewObjName("feature_test"), |
| } |
| |
| fd, err := sys.MapCreate(&attr) |
| if err != nil { |
| return internal.ErrNotSupported |
| } |
| |
| _ = fd.Close() |
| return nil |
| }) |
| |
| var objNameAllowsDot = internal.NewFeatureTest("dot in object names", "5.2", func() error { |
| if err := haveObjName(); err != nil { |
| return err |
| } |
| |
| attr := sys.MapCreateAttr{ |
| MapType: sys.MapType(Array), |
| KeySize: 4, |
| ValueSize: 4, |
| MaxEntries: 1, |
| MapName: sys.NewObjName(".test"), |
| } |
| |
| fd, err := sys.MapCreate(&attr) |
| if err != nil { |
| return internal.ErrNotSupported |
| } |
| |
| _ = fd.Close() |
| return nil |
| }) |
| |
| var haveBatchAPI = internal.NewFeatureTest("map batch api", "5.6", func() error { |
| var maxEntries uint32 = 2 |
| attr := sys.MapCreateAttr{ |
| MapType: sys.MapType(Hash), |
| KeySize: 4, |
| ValueSize: 4, |
| MaxEntries: maxEntries, |
| } |
| |
| fd, err := sys.MapCreate(&attr) |
| if err != nil { |
| return internal.ErrNotSupported |
| } |
| defer fd.Close() |
| |
| keys := []uint32{1, 2} |
| values := []uint32{3, 4} |
| kp, _ := marshalPtr(keys, 8) |
| vp, _ := marshalPtr(values, 8) |
| |
| err = sys.MapUpdateBatch(&sys.MapUpdateBatchAttr{ |
| MapFd: fd.Uint(), |
| Keys: kp, |
| Values: vp, |
| Count: maxEntries, |
| }) |
| if err != nil { |
| return internal.ErrNotSupported |
| } |
| return nil |
| }) |
| |
| var haveProbeReadKernel = internal.NewFeatureTest("bpf_probe_read_kernel", "5.5", func() error { |
| insns := asm.Instructions{ |
| asm.Mov.Reg(asm.R1, asm.R10), |
| asm.Add.Imm(asm.R1, -8), |
| asm.Mov.Imm(asm.R2, 8), |
| asm.Mov.Imm(asm.R3, 0), |
| asm.FnProbeReadKernel.Call(), |
| asm.Return(), |
| } |
| |
| fd, err := progLoad(insns, Kprobe, "GPL") |
| if err != nil { |
| return internal.ErrNotSupported |
| } |
| _ = fd.Close() |
| return nil |
| }) |
| |
| var haveBPFToBPFCalls = internal.NewFeatureTest("bpf2bpf calls", "4.16", func() error { |
| insns := asm.Instructions{ |
| asm.Call.Label("prog2").WithSymbol("prog1"), |
| asm.Return(), |
| asm.Mov.Imm(asm.R0, 0).WithSymbol("prog2"), |
| asm.Return(), |
| } |
| |
| fd, err := progLoad(insns, SocketFilter, "MIT") |
| if errors.Is(err, unix.EINVAL) { |
| return internal.ErrNotSupported |
| } |
| if err != nil { |
| return err |
| } |
| _ = fd.Close() |
| return nil |
| }) |
| |
| var haveSyscallWrapper = internal.NewFeatureTest("syscall wrapper", "4.17", func() error { |
| prefix := internal.PlatformPrefix() |
| if prefix == "" { |
| return fmt.Errorf("unable to find the platform prefix for (%s)", runtime.GOARCH) |
| } |
| |
| args := tracefs.ProbeArgs{ |
| Type: tracefs.Kprobe, |
| Symbol: prefix + "sys_bpf", |
| Pid: -1, |
| } |
| |
| var err error |
| args.Group, err = tracefs.RandomGroup("ebpf_probe") |
| if err != nil { |
| return err |
| } |
| |
| evt, err := tracefs.NewEvent(args) |
| if errors.Is(err, os.ErrNotExist) { |
| return internal.ErrNotSupported |
| } |
| if err != nil { |
| return err |
| } |
| |
| return evt.Close() |
| }) |