Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<img src="https://coroot.com/static/img/blog/ebpf.svg" width="800" />

Expand Down
22 changes: 22 additions & 0 deletions ebpftracer/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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},
})
Expand Down
40 changes: 40 additions & 0 deletions ebpftracer/tracer_progtype_test.go
Original file line number Diff line number Diff line change
@@ -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())
})
}