package features import ( "bytes" "errors" "fmt" "sync" "github.com/cilium/ebpf" "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/sys" "github.com/cilium/ebpf/internal/unix" ) func init() { miscs.miscTypes = make(map[miscType]error) } var ( miscs miscCache ) type miscCache struct { sync.Mutex miscTypes map[miscType]error } type miscType uint32 const ( // largeInsn support introduced in Linux 5.2 // commit c04c0d2b968ac45d6ef020316808ef6c82325a82 largeInsn miscType = iota // boundedLoops support introduced in Linux 5.3 // commit 2589726d12a1b12eaaa93c7f1ea64287e383c7a5 boundedLoops // v2ISA support introduced in Linux 4.14 // commit 92b31a9af73b3a3fc801899335d6c47966351830 v2ISA // v3ISA support introduced in Linux 5.1 // commit 092ed0968bb648cd18e8a0430cd0a8a71727315c v3ISA ) const ( maxInsns = 4096 ) // HaveLargeInstructions probes the running kernel if more than 4096 instructions // per program are supported. // // See the package documentation for the meaning of the error return value. func HaveLargeInstructions() error { return probeMisc(largeInsn) } // HaveBoundedLoops probes the running kernel if bounded loops are supported. // // See the package documentation for the meaning of the error return value. func HaveBoundedLoops() error { return probeMisc(boundedLoops) } // HaveV2ISA probes the running kernel if instructions of the v2 ISA are supported. // // See the package documentation for the meaning of the error return value. func HaveV2ISA() error { return probeMisc(v2ISA) } // HaveV3ISA probes the running kernel if instructions of the v3 ISA are supported. // // See the package documentation for the meaning of the error return value. func HaveV3ISA() error { return probeMisc(v3ISA) } // probeMisc checks the kernel for a given supported misc by creating // a specialized program probe and loading it. func probeMisc(mt miscType) error { mc.Lock() defer mc.Unlock() err, ok := miscs.miscTypes[mt] if ok { return err } attr, err := createMiscProbeAttr(mt) if err != nil { return fmt.Errorf("couldn't create the attributes for the probe: %w", err) } fd, err := sys.ProgLoad(attr) 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 map type. case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG): err = ebpf.ErrNotSupported // EPERM is kept as-is and is not converted or wrapped. case errors.Is(err, unix.EPERM): break // Wrap unexpected errors. case err != nil: err = fmt.Errorf("unexpected error during feature probe: %w", err) default: fd.Close() } miscs.miscTypes[mt] = err return err } func createMiscProbeAttr(mt miscType) (*sys.ProgLoadAttr, error) { var insns asm.Instructions switch mt { case largeInsn: for i := 0; i < maxInsns; i++ { insns = append(insns, asm.Mov.Imm(asm.R0, 1)) } insns = append(insns, asm.Return()) case boundedLoops: insns = 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(), } case v2ISA: insns = 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"), } case v3ISA: insns = 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"), } default: return nil, fmt.Errorf("misc probe %d not implemented", mt) } 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) return &sys.ProgLoadAttr{ ProgType: sys.BPF_PROG_TYPE_SOCKET_FILTER, Insns: instructions, InsnCnt: uint32(len(bytecode) / asm.InstructionSize), License: sys.NewStringPointer("MIT"), }, nil }