diff --git a/README.md b/README.md index bcc2609..7fa6206 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ The agent gathers metrics related to a node and the containers running on it, and it exposes them in the Prometheus format. It uses eBPF to track container related events such as TCP connects, so the minimum supported Linux kernel version is 5.1. +The kernel must also be built with `CONFIG_BPF_EVENTS=y` (kprobe and tracepoint BPF programs); some embedded and vendor kernels disable it. diff --git a/ebpftracer/tracer.go b/ebpftracer/tracer.go index de1e336..730ce3b 100644 --- a/ebpftracer/tracer.go +++ b/ebpftracer/tracer.go @@ -18,6 +18,7 @@ import ( "time" "github.com/cilium/ebpf" + "github.com/cilium/ebpf/features" "github.com/cilium/ebpf/link" "github.com/cilium/ebpf/perf" "github.com/coroot/coroot-node-agent/common" @@ -237,6 +238,24 @@ type Connection struct { _ [7]uint8 } +var haveProgramType = features.HaveProgramType + +// checkProgramTypes verifies that the kernel supports the BPF program types used by the collection +// (uprobes are BPF_PROG_TYPE_KPROBE programs). Kernels built without CONFIG_BPF_EVENTS reject +// them with a bare EINVAL, which is indistinguishable from other load failures without this probe. +// Only a conclusive "not supported" result is reported; any other probe failure is left +// for the actual collection load to surface. The returned error wraps ebpf.ErrNotSupported +// rather than the probe error, whose text names the kernel version that introduced the +// program type and would point away from the kernel configuration. +func checkProgramTypes() error { + for _, pt := range []ebpf.ProgramType{ebpf.TracePoint, ebpf.Kprobe} { + if err := haveProgramType(pt); errors.Is(err, ebpf.ErrNotSupported) { + return fmt.Errorf("kernel does not support BPF %s programs (CONFIG_BPF_EVENTS is not set?): %w", pt, ebpf.ErrNotSupported) + } + } + return nil +} + type perfMap struct { name string perCPUBufferSizePages int @@ -294,6 +313,9 @@ func (t *Tracer) ebpf(ch chan<- Event) error { return fmt.Errorf("failed to load collection spec: %w", err) } _ = unix.Setrlimit(unix.RLIMIT_MEMLOCK, &unix.Rlimit{Cur: unix.RLIM_INFINITY, Max: unix.RLIM_INFINITY}) + if err = checkProgramTypes(); err != nil { + return err + } c, err := ebpf.NewCollectionWithOptions(collectionSpec, ebpf.CollectionOptions{ //Programs: ebpf.ProgramOptions{LogLevel: 2, LogSize: 20 * 1024 * 1024}, }) diff --git a/ebpftracer/tracer_progtype_test.go b/ebpftracer/tracer_progtype_test.go new file mode 100644 index 0000000..5418de7 --- /dev/null +++ b/ebpftracer/tracer_progtype_test.go @@ -0,0 +1,40 @@ +package ebpftracer + +import ( + "fmt" + "testing" + + "github.com/cilium/ebpf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" +) + +func TestCheckProgramTypes(t *testing.T) { + orig := haveProgramType + defer func() { haveProgramType = orig }() + + t.Run("supported", func(t *testing.T) { + haveProgramType = func(ebpf.ProgramType) error { return nil } + assert.NoError(t, checkProgramTypes()) + }) + + t.Run("not supported", func(t *testing.T) { + // Shaped like the *ebpf.UnsupportedFeatureError returned by features.HaveProgramType. + haveProgramType = func(pt ebpf.ProgramType) error { + if pt == ebpf.Kprobe { + return fmt.Errorf("Kprobe not supported (requires >= v4.1): %w", ebpf.ErrNotSupported) + } + return nil + } + err := checkProgramTypes() + require.Error(t, err) + assert.ErrorIs(t, err, ebpf.ErrNotSupported) + assert.EqualError(t, err, "kernel does not support BPF Kprobe programs (CONFIG_BPF_EVENTS is not set?): not supported") + }) + + t.Run("inconclusive probe", func(t *testing.T) { + haveProgramType = func(ebpf.ProgramType) error { return unix.EPERM } + assert.NoError(t, checkProgramTypes()) + }) +}