Chapter 10: Native eBPF
Overview
Tier 3 is the future of Linux observability: loading eBPF programs directly from Go code without Python, without external binaries, without needing the bcc-tools package installed.
melisai's internal/ebpf/ package provides the infrastructure for this.
Source Files: ebpf/ (3 files)
| File | Lines | Purpose |
|---|---|---|
btf.go |
59 | BTF (BPF Type Format) detection |
loader.go |
131 | eBPF program loader |
capabilities.go |
~50 | Kernel/eBPF capability assessment |
BTF and CO-RE
What is BTF?
BTF (BPF Type Format) is metadata that describes kernel data structures — their names, fields, sizes, and offsets. Without BTF, an eBPF program compiled on one kernel version might not work on another because struct layouts change.
What is CO-RE?
CO-RE (Compile Once, Run Everywhere) uses BTF to relocate struct field accesses at load time:
┌─────────────────────────┐ ┌─────────────────────────┐
│ Compiled on kernel 5.4 │ │ Running on kernel 6.1 │
│ │ │ │
│ task_struct.comm │ │ task_struct.comm │
│ offset: 1240 │ ──► │ offset: 1312 │
│ │ BTF │ (field moved!) │
│ eBPF reads offset 1240 │reloc │ eBPF reads offset 1312 │
└─────────────────────────┘ └─────────────────────────┘
Without CO-RE, you'd need to recompile the eBPF program for every kernel version.
BTF Detection
func HasBTF() bool {
// Check for /sys/kernel/btf/vmlinux
_, err := os.Stat("/sys/kernel/btf/vmlinux")
return err == nil
}
BTF is available on kernels ≥ 5.4 when compiled with CONFIG_DEBUG_INFO_BTF=y. Most modern distros (Ubuntu 20.10+, Fedora 31+, RHEL 9+) have BTF enabled by default.
Kernel Version Parsing
func ParseKernelVersion(version string) (major, minor, patch int) {
// "5.15.0-91-generic" → (5, 15, 0)
parts := strings.SplitN(version, ".", 3)
major, _ = strconv.Atoi(parts[0])
minor, _ = strconv.Atoi(parts[1])
// Patch may contain suffix like "-91-generic"
patchStr := strings.SplitN(parts[2], "-", 2)[0]
patch, _ = strconv.Atoi(patchStr)
}
The Tier Decision
func AssessCapabilities() Capabilities {
caps := Capabilities{Tier: 1} // Always at least Tier 1
// Tier 2: BCC tools available?
if BCCToolsAvailable() {
caps.Tier = 2
caps.BCCAvailable = true
}
// Tier 3: Native eBPF possible?
if HasBTF() && KernelVersion >= (5, 8, 0) && os.Geteuid() == 0 {
caps.Tier = 3
caps.BTFAvailable = true
}
}
The Loader (Real Implementation)
loader.go uses the cilium/ebpf library to load and attach programs:
type Loader struct {
btfInfo *BTFInfo
verbose bool
}
func (l *Loader) TryLoad(ctx context.Context, spec *ProgramSpec) (*LoadedProgram, error) {
// 1. Load compiled BPF object (.o file)
// spec.ObjectFile points to the file compiled with clang
collSpec, err := ebpf.LoadCollectionSpec(spec.ObjectFile)
// 2. Load into kernel (verifies and JITs)
// This step performs CO-RE relocations automatically
coll, err := ebpf.NewCollection(collSpec)
// 3. Attach kprobe
kp, err := link.Kprobe(spec.AttachTo, prog, nil)
return &LoadedProgram{Collection: coll, Link: kp}, nil
}
The system requires .o files (compiled eBPF bytecode) to be present. In the future, these can be embedded into the binary using Go's //go:embed directive.
Native Collectors
The NativeTcpretransCollector (internal/collector/ebpf_tcpretrans.go) demonstrates how to use this:
- Reads Perf Buffer: Uses
perf.NewReaderto read events from the BPF map. - Parses Binary Data: Uses
binary.Readto convert raw bytes into Go structs. - No Context Switch Overhead (userspace): Unlike BCC (which calls Python -> C -> Go), this is pure Go -> Kernel.
Why Tier 3 Matters
| Feature | Tier 2 (BCC) | Tier 3 (Native) |
|---|---|---|
| Dependencies | Python, bcc-tools package | None (embedded in binary) |
| Startup time | 1-3 seconds per tool | < 100ms |
| Memory | 50-100MB per tool (Python) | < 5MB per program |
| Kernel version | Any kernel with BPF | ≥ 5.8 with BTF |
| Security | External binary verification | Embedded programs, no external trust |