diff --git a/bpf/maps/sock_dir.h b/bpf/maps/sock_dir.h index 5e96552992..8f4be92518 100644 --- a/bpf/maps/sock_dir.h +++ b/bpf/maps/sock_dir.h @@ -6,6 +6,8 @@ #include #include +#include + // A map of sockets which we track with sock_ops. The sock_msg // program subscribes to this map and runs for each new socket // activity @@ -16,4 +18,5 @@ struct { __uint(max_entries, 65535); __uint(key_size, sizeof(u64)); __uint(value_size, sizeof(u32)); + __uint(pinning, OBI_PIN_INTERNAL); } sock_dir SEC(".maps"); diff --git a/bpf/tpinjector/sock_iter.c b/bpf/tpinjector/sock_iter.c index 7b1317ef36..2fa92433af 100644 --- a/bpf/tpinjector/sock_iter.c +++ b/bpf/tpinjector/sock_iter.c @@ -12,6 +12,8 @@ #include +char __license[] SEC("license") = "Dual MIT/GPL"; + // max IPv6+port: "[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535" = 48 chars enum { k_addr_buf_len = 48 }; diff --git a/bpf/tpinjector/tpinjector.c b/bpf/tpinjector/tpinjector.c index b679d1d494..a35a98c412 100644 --- a/bpf/tpinjector/tpinjector.c +++ b/bpf/tpinjector/tpinjector.c @@ -975,5 +975,3 @@ int obi_packet_extender_create_tp(struct sk_msg_md *msg) { return SK_PASS; } - -#include "sock_iter.c" diff --git a/pkg/ebpf/common/spec.go b/pkg/ebpf/common/spec.go new file mode 100644 index 0000000000..5ff325fb5f --- /dev/null +++ b/pkg/ebpf/common/spec.go @@ -0,0 +1,16 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package ebpfcommon // import "go.opentelemetry.io/obi/pkg/ebpf/common" + +import "github.com/cilium/ebpf" + +// SpecBundle groups everything needed to load one BPF collection into the kernel. +type SpecBundle struct { + // Spec is the BPF collection spec to load, as generated by bpf2go. + Spec *ebpf.CollectionSpec + // Objects is the pointer to the generated BpfObjects struct that Spec.LoadAndAssign will populate. + Objects any + // Constants is an optional map of BPF constants to rewrite before loading. + Constants map[string]any +} diff --git a/pkg/ebpf/tracer.go b/pkg/ebpf/tracer.go index 8213688b50..534aa6e4b1 100644 --- a/pkg/ebpf/tracer.go +++ b/pkg/ebpf/tracer.go @@ -64,14 +64,13 @@ type PIDsAccounter interface { } type CommonTracer interface { - // Load the bpf object that is generated by the bpf2go compiler - Load() (*ebpf.CollectionSpec, error) + // LoadSpecs returns one SpecBundle per BPF collection. Each bundle contains + // the collection spec, the object pointer to populate, and the constants to rewrite. + LoadSpecs() ([]*ebpfcommon.SpecBundle, error) // AddCloser adds io.Closer instances that need to be invoked when the // Run function ends. AddCloser(c ...io.Closer) - // BpfObjects that are created by the bpf2go compiler - BpfObjects() any - // Sets up any tail call tables if the BPF program has it + // SetupTailCalls sets up any tail call jump tables after all specs are loaded. SetupTailCalls() } @@ -87,9 +86,6 @@ type KprobesTracer interface { type Tracer interface { PIDsAccounter KprobesTracer - // Constants returns a map of constants to be overridden into the eBPF program. - // The key is the constant name and the value is the value to overwrite. - Constants() map[string]any // GoProbes returns a slice with the name of Go functions that need to be inspected // in the executable, as well as the eBPF programs that optionally need to be // inserted as the Go function start and end probes diff --git a/pkg/ebpf/tracer_linux.go b/pkg/ebpf/tracer_linux.go index 06c353edd6..b0813afeb6 100644 --- a/pkg/ebpf/tracer_linux.go +++ b/pkg/ebpf/tracer_linux.go @@ -99,6 +99,34 @@ func resolveMaps(eventContext *common.EBPFEventContext, spec *ebpf.CollectionSpe return &collOpts, nil } +func loadSpec(eventContext *common.EBPFEventContext, bundle *common.SpecBundle, otelBPFFSPath string, idx int) error { + if err := ebpfconvenience.RewriteConstants(bundle.Spec, bundle.Constants); err != nil { + return fmt.Errorf("rewriting BPF constants for spec %d: %w", idx, err) + } + + collOpts, err := resolveMaps(eventContext, bundle.Spec) + if err != nil { + return fmt.Errorf("resolving maps for spec %d: %w", idx, err) + } + + collOpts.Programs = ebpf.ProgramOptions{LogSizeStart: 640 * 1024} + collOpts.Maps = ebpf.MapOptions{PinPath: otelBPFFSPath} + + if err := bundle.Spec.LoadAndAssign(bundle.Objects, collOpts); err != nil { + return fmt.Errorf("loading spec %d: %w", idx, err) + } + + return nil +} + +func closeLoadedSpecs(bundles []*common.SpecBundle) { + for _, bundle := range bundles { + if c, ok := bundle.Objects.(io.Closer); ok { + c.Close() + } + } +} + func unloadInternalMaps(eventContext *common.EBPFEventContext) { eventContext.MapsLock.Lock() defer eventContext.MapsLock.Unlock() @@ -173,18 +201,6 @@ func (pt *ProcessTracer) Run(ctx context.Context, ebpfEventContext *common.EBPFE } } -func (pt *ProcessTracer) loadSpec(p Tracer) (*ebpf.CollectionSpec, error) { - spec, err := p.Load() - if err != nil { - return nil, fmt.Errorf("loading eBPF program: %w", err) - } - if err := ebpfconvenience.RewriteConstants(spec, p.Constants()); err != nil { - return nil, fmt.Errorf("rewriting BPF constants definition: %w", err) - } - - return spec, nil -} - func (pt *ProcessTracer) makeOtelBPFFSPath() (string, error) { otelPath := path.Join(pt.bpffsPath, "otel") @@ -195,20 +211,28 @@ func (pt *ProcessTracer) makeOtelBPFFSPath() (string, error) { return otelPath, nil } -func (pt *ProcessTracer) setupBPFFS(spec *ebpf.CollectionSpec) string { +func (pt *ProcessTracer) setupOtelBPFFSPath(bundles []*common.SpecBundle) string { + // Set up BPF FS path once for all specs otelBPFFSPath, err := pt.makeOtelBPFFSPath() if err == nil { return otelBPFFSPath } - slog.Warn("creating OTEL namespace in bpffs failed (is bpffs mounted?)", "bpffs_path", pt.bpffsPath, "err", err) - slog.Warn("OBI will still work, but features depending on pinned maps (e.g., log enricher, profile correlation) will be disabled") + log := ptlog() + + log.Warn("creating OTEL namespace in bpffs failed (is bpffs mounted?)", + "bpffs_path", pt.bpffsPath, "err", err) + + log.Warn("OBI will still work, but features depending on pinned maps (e.g., log enricher, profile correlation) will be disabled") - for _, v := range spec.Maps { - if v.Pinning == ebpf.PinByName { - v.Pinning = ebpf.PinNone - v.MaxEntries = 1 + // disable pinning for ALL specs + for _, bundle := range bundles { + for _, v := range bundle.Spec.Maps { + if v.Pinning == ebpf.PinByName { + v.Pinning = ebpf.PinNone + v.MaxEntries = 1 + } } } @@ -216,22 +240,21 @@ func (pt *ProcessTracer) setupBPFFS(spec *ebpf.CollectionSpec) string { } func (pt *ProcessTracer) loadAndAssign(eventContext *common.EBPFEventContext, p Tracer) error { - spec, err := pt.loadSpec(p) - if err != nil { - return err - } - - collOpts, err := resolveMaps(eventContext, spec) + bundles, err := p.LoadSpecs() if err != nil { - return err + return fmt.Errorf("loading eBPF program specs: %w", err) } - otelBPFFSPath := pt.setupBPFFS(spec) + otelBPFFSPath := pt.setupOtelBPFFSPath(bundles) - collOpts.Programs = ebpf.ProgramOptions{LogSizeStart: 640 * 1024} - collOpts.Maps = ebpf.MapOptions{PinPath: otelBPFFSPath} + for i, bundle := range bundles { + if err := loadSpec(eventContext, bundle, otelBPFFSPath, i); err != nil { + closeLoadedSpecs(bundles[:i]) + return err + } + } - return spec.LoadAndAssign(p.BpfObjects(), collOpts) + return nil } func (pt *ProcessTracer) loadTracer(eventContext *common.EBPFEventContext, p Tracer, log *slog.Logger) error { @@ -414,19 +437,18 @@ func RunUtilityTracer(ctx context.Context, eventContext *common.EBPFEventContext i := instrumenter{} plog := ptlog() plog.Debug("loading independent eBPF program") - spec, err := p.Load() - if err != nil { - return fmt.Errorf("loading eBPF program: %w", err) - } - collOpts, err := resolveMaps(eventContext, spec) + bundles, err := p.LoadSpecs() if err != nil { - return err + return fmt.Errorf("loading eBPF program specs: %w", err) } - if err := spec.LoadAndAssign(p.BpfObjects(), collOpts); err != nil { - printVerifierErrorInfo(err) - return fmt.Errorf("loading and assigning BPF objects: %w", err) + for idx, bundle := range bundles { + if err := loadSpec(eventContext, bundle, "", idx); err != nil { + closeLoadedSpecs(bundles[:idx]) + printVerifierErrorInfo(err) + return err + } } if err := i.kprobes(p); err != nil { diff --git a/pkg/internal/ebpf/generictracer/generictracer.go b/pkg/internal/ebpf/generictracer/generictracer.go index bcd7f31a9b..59cb2b60a4 100644 --- a/pkg/internal/ebpf/generictracer/generictracer.go +++ b/pkg/internal/ebpf/generictracer/generictracer.go @@ -127,7 +127,7 @@ func (p *Tracer) BlockPID(pid app.PID, ns uint32) { p.rebuildValidPids() } -func (p *Tracer) Load() (*ebpf.CollectionSpec, error) { +func (p *Tracer) LoadSpecs() ([]*ebpfcommon.SpecBundle, error) { if p.cfg.EBPF.TrackRequestHeaders || p.cfg.EBPF.ContextPropagation.IsEnabled() { p.log.Info("Enabling trace information parsing", "bpf_loop_enabled", ebpfcommon.SupportsEBPFLoops(p.log, p.cfg.EBPF.OverrideBPFLoopEnabled)) @@ -140,7 +140,7 @@ func (p *Tracer) Load() (*ebpf.CollectionSpec, error) { ebpfcommon.FixupSpec(spec, p.cfg.EBPF.OverrideBPFLoopEnabled) - return spec, err + return []*ebpfcommon.SpecBundle{{Spec: spec, Objects: &p.bpfObjects, Constants: p.constants()}}, nil } func (p *Tracer) SetupTailCalls() { @@ -162,7 +162,7 @@ func (p *Tracer) SetupTailCalls() { } } -func (p *Tracer) Constants() map[string]any { +func (p *Tracer) constants() map[string]any { m := make(map[string]any, 2) m["wakeup_data_bytes"] = uint32(p.cfg.EBPF.WakeupLen) * uint32(unsafe.Sizeof(ebpfcommon.HTTPRequestTrace{})) @@ -212,10 +212,6 @@ func (p *Tracer) RegisterOffsets(_ *exec.FileInfo, _ *goexec.Offsets) {} func (p *Tracer) ProcessBinary(_ *exec.FileInfo) {} -func (p *Tracer) BpfObjects() any { - return &p.bpfObjects -} - func (p *Tracer) AddCloser(c ...io.Closer) { p.closers = append(p.closers, c...) } diff --git a/pkg/internal/ebpf/generictracer/generictracer_notlinux.go b/pkg/internal/ebpf/generictracer/generictracer_notlinux.go index fc25eca7a9..8e4f8bbff1 100644 --- a/pkg/internal/ebpf/generictracer/generictracer_notlinux.go +++ b/pkg/internal/ebpf/generictracer/generictracer_notlinux.go @@ -29,8 +29,7 @@ type Tracer struct{} func New(_ ebpfcommon.ServiceFilter, _ *obi.Config, _ imetrics.Reporter) *Tracer { return nil } func (p *Tracer) AllowPID(_ app.PID, _ uint32, _ *svc.Attrs) {} func (p *Tracer) BlockPID(_ app.PID, _ uint32) {} -func (p *Tracer) Load() (*ebpf.CollectionSpec, error) { return nil, nil } -func (p *Tracer) BpfObjects() any { return nil } +func (p *Tracer) LoadSpecs() ([]*ebpfcommon.SpecBundle, error) { return nil, nil } func (p *Tracer) AddCloser(_ ...io.Closer) {} func (p *Tracer) GoProbes() map[string][]*ebpfcommon.ProbeDesc { return nil } func (p *Tracer) KProbes() map[string]ebpfcommon.ProbeDesc { return nil } @@ -47,7 +46,6 @@ func (p *Tracer) UnlinkInstrumentedLib(_ uint64) func (p *Tracer) AlreadyInstrumentedLib(_ uint64) bool { return false } func (p *Tracer) Run(_ context.Context, _ *ebpfcommon.EBPFEventContext, _ *msg.Queue[[]request.Span]) { } -func (p *Tracer) Constants() map[string]any { return nil } func (p *Tracer) SetupTailCalls() {} func (p *Tracer) RegisterOffsets(_ *exec.FileInfo, _ *goexec.Offsets) {} func (p *Tracer) ProcessBinary(_ *exec.FileInfo) {} diff --git a/pkg/internal/ebpf/gotracer/gotracer.go b/pkg/internal/ebpf/gotracer/gotracer.go index 000cc0071b..a7a02443d6 100644 --- a/pkg/internal/ebpf/gotracer/gotracer.go +++ b/pkg/internal/ebpf/gotracer/gotracer.go @@ -84,18 +84,24 @@ func (p *Tracer) supportsContextPropagation() bool { return !ebpfcommon.IntegrityModeOverride && ebpfcommon.SupportsContextPropagationWithProbe(p.log) } -func (p *Tracer) Load() (*ebpf.CollectionSpec, error) { +func (p *Tracer) LoadSpecs() ([]*ebpfcommon.SpecBundle, error) { if !p.supportsContextPropagation() { p.log.Info("Kernel in lockdown mode or missing CAP_SYS_ADMIN.") } - return LoadBpf() -} + spec, err := LoadBpf() + if err != nil { + return nil, err + } -func (p *Tracer) SetupTailCalls() { + return []*ebpfcommon.SpecBundle{{ + Spec: spec, + Objects: &p.bpfObjects, + Constants: p.constants(), + }}, nil } -func (p *Tracer) Constants() map[string]any { +func (p *Tracer) constants() map[string]any { blackBoxCP := uint32(0) if p.cfg.DisableBlackBoxCP { blackBoxCP = uint32(1) @@ -120,6 +126,8 @@ func (p *Tracer) Constants() map[string]any { } } +func (p *Tracer) SetupTailCalls() {} + func (p *Tracer) RegisterOffsets(fileInfo *exec.FileInfo, offsets *goexec.Offsets) { offTable := BpfOffTableT{} // Set the field offsets and the logLevel for the Go BPF program in a map @@ -246,10 +254,6 @@ func (p *Tracer) RegisterOffsets(fileInfo *exec.FileInfo, offsets *goexec.Offset func (p *Tracer) ProcessBinary(_ *exec.FileInfo) {} -func (p *Tracer) BpfObjects() any { - return &p.bpfObjects -} - func (p *Tracer) AddCloser(c ...io.Closer) { p.closers = append(p.closers, c...) } diff --git a/pkg/internal/ebpf/gpuevent/gpuevent.go b/pkg/internal/ebpf/gpuevent/gpuevent.go index 01d1dfb750..9cc79a5fa6 100644 --- a/pkg/internal/ebpf/gpuevent/gpuevent.go +++ b/pkg/internal/ebpf/gpuevent/gpuevent.go @@ -86,34 +86,34 @@ func (p *Tracer) BlockPID(pid app.PID, ns uint32) { p.pidsFilter.BlockPID(pid, ns) } -func (p *Tracer) Load() (*ebpf.CollectionSpec, error) { - return LoadBpf() -} +func (p *Tracer) LoadSpecs() ([]*ebpfcommon.SpecBundle, error) { + spec, err := LoadBpf() + if err != nil { + return nil, err + } -func (p *Tracer) Constants() map[string]any { - m := make(map[string]any, 2) + return []*ebpfcommon.SpecBundle{{Spec: spec, Objects: &p.bpfObjects, Constants: p.constants()}}, nil +} +func (p *Tracer) constants() map[string]any { // The eBPF side does some basic filtering of events that do not belong to // processes which we monitor. We filter more accurately in the userspace, but // for performance reasons we enable the PID based filtering in eBPF. + filterPids := int32(1) if p.cfg.Discovery.BPFPidFilterOff { - m["filter_pids"] = int32(0) - } else { - m["filter_pids"] = int32(1) + filterPids = int32(0) } - m["g_bpf_debug"] = p.cfg.EBPF.BpfDebug - return m + return map[string]any{ + "filter_pids": filterPids, + "g_bpf_debug": p.cfg.EBPF.BpfDebug, + } } func (p *Tracer) RegisterOffsets(_ *exec.FileInfo, _ *goexec.Offsets) {} func (p *Tracer) ProcessBinary(_ *exec.FileInfo) {} -func (p *Tracer) BpfObjects() any { - return &p.bpfObjects -} - func (p *Tracer) AddCloser(c ...io.Closer) { p.closers = append(p.closers, c...) } diff --git a/pkg/internal/ebpf/logenricher/logenricher.go b/pkg/internal/ebpf/logenricher/logenricher.go index 393bfe57cd..f14c2290fd 100644 --- a/pkg/internal/ebpf/logenricher/logenricher.go +++ b/pkg/internal/ebpf/logenricher/logenricher.go @@ -90,26 +90,28 @@ func New(cfg *obi.Config) *Tracer { return tr } -func (p *Tracer) Load() (*ebpf.CollectionSpec, error) { - return LoadBpf() +func (p *Tracer) LoadSpecs() ([]*ebpfcommon.SpecBundle, error) { + spec, err := LoadBpf() + if err != nil { + return nil, err + } + return []*ebpfcommon.SpecBundle{{ + Spec: spec, + Objects: &p.bpfObjects, + Constants: p.constants(), + }}, nil } -func (p *Tracer) SetupTailCalls() {} - -func (p *Tracer) Constants() map[string]any { - return map[string]any{ - "g_bpf_debug": p.cfg.EBPF.BpfDebug, - } +func (p *Tracer) constants() map[string]any { + return map[string]any{"g_bpf_debug": p.cfg.EBPF.BpfDebug} } +func (p *Tracer) SetupTailCalls() {} + func (p *Tracer) RegisterOffsets(_ *exec.FileInfo, _ *goexec.Offsets) {} func (p *Tracer) ProcessBinary(_ *exec.FileInfo) {} -func (p *Tracer) BpfObjects() any { - return &p.bpfObjects -} - func (p *Tracer) AddCloser(c ...io.Closer) { p.closers = append(p.closers, c...) } diff --git a/pkg/internal/ebpf/logenricher/logenricher_notlinux.go b/pkg/internal/ebpf/logenricher/logenricher_notlinux.go index 2f8ef2448b..36eb6c5187 100644 --- a/pkg/internal/ebpf/logenricher/logenricher_notlinux.go +++ b/pkg/internal/ebpf/logenricher/logenricher_notlinux.go @@ -26,8 +26,7 @@ type Tracer struct{} func New(_ *obi.Config) *Tracer { return nil } func (p *Tracer) AllowPID(_ app.PID, _ uint32, _ *svc.Attrs) {} func (p *Tracer) BlockPID(_ app.PID, _ uint32) {} -func (p *Tracer) Load() (*ebpf.CollectionSpec, error) { return nil, nil } -func (p *Tracer) BpfObjects() any { return nil } +func (p *Tracer) LoadSpecs() ([]*ebpfcommon.SpecBundle, error) { return nil, nil } func (p *Tracer) AddCloser(_ ...io.Closer) {} func (p *Tracer) GoProbes() map[string][]*ebpfcommon.ProbeDesc { return nil } func (p *Tracer) KProbes() map[string]ebpfcommon.ProbeDesc { return nil } @@ -44,7 +43,6 @@ func (p *Tracer) UnlinkInstrumentedLib(_ uint64) {} func (p *Tracer) AlreadyInstrumentedLib(_ uint64) bool { return false } func (p *Tracer) Run(_ context.Context, _ *ebpfcommon.EBPFEventContext, _ *msg.Queue[[]request.Span]) { } -func (p *Tracer) Constants() map[string]any { return nil } func (p *Tracer) SetupTailCalls() {} func (p *Tracer) RegisterOffsets(_ *exec.FileInfo, _ *goexec.Offsets) {} func (p *Tracer) ProcessBinary(_ *exec.FileInfo) {} diff --git a/pkg/internal/ebpf/logger/logger.go b/pkg/internal/ebpf/logger/logger.go index ebe568e325..8ee0242ad9 100644 --- a/pkg/internal/ebpf/logger/logger.go +++ b/pkg/internal/ebpf/logger/logger.go @@ -9,7 +9,6 @@ import ( "io" "log/slog" - "github.com/cilium/ebpf" "golang.org/x/sys/unix" "go.opentelemetry.io/obi/pkg/appolly/app/request" @@ -42,15 +41,23 @@ func New(cfg *obi.Config) *BPFLogger { } } -func (p *BPFLogger) Load() (*ebpf.CollectionSpec, error) { +func (p *BPFLogger) LoadSpecs() ([]*ebpfcommon.SpecBundle, error) { if p.cfg.EBPF.BpfDebug { - return LoadBpf() + spec, err := LoadBpf() + if err != nil { + return nil, err + } + return []*ebpfcommon.SpecBundle{{ + Spec: spec, + Objects: &p.bpfObjects, + Constants: p.constants(), + }}, nil } return nil, errors.New("BPF debug is not enabled") } -func (p *BPFLogger) BpfObjects() any { - return &p.bpfObjects +func (p *BPFLogger) constants() map[string]any { + return map[string]any{"g_bpf_debug": p.cfg.EBPF.BpfDebug} } func (p *BPFLogger) AddCloser(c ...io.Closer) { @@ -65,12 +72,6 @@ func (p *BPFLogger) Tracepoints() map[string]ebpfcommon.ProbeDesc { return nil } -func (p *BPFLogger) Constants() map[string]any { - return map[string]any{ - "g_bpf_debug": p.cfg.EBPF.BpfDebug, - } -} - func (p *BPFLogger) SetupTailCalls() {} func (p *BPFLogger) Run(ctx context.Context) { diff --git a/pkg/internal/ebpf/tpinjector/tpinjector.go b/pkg/internal/ebpf/tpinjector/tpinjector.go index 2ea4ca4f5a..0c8692ec5f 100644 --- a/pkg/internal/ebpf/tpinjector/tpinjector.go +++ b/pkg/internal/ebpf/tpinjector/tpinjector.go @@ -23,13 +23,15 @@ import ( ) //go:generate $BPF2GO -cc $BPF_CLANG -cflags $BPF_CFLAGS -target amd64,arm64 Bpf ../../../../bpf/tpinjector/tpinjector.c -- -I../../../../bpf -I../../../../bpf +//go:generate $BPF2GO -cc $BPF_CLANG -cflags $BPF_CFLAGS -target amd64,arm64 BpfIter ../../../../bpf/tpinjector/sock_iter.c -- -I../../../../bpf -I../../../../bpf type Tracer struct { - cfg *obi.Config - bpfObjects BpfObjects - closers []io.Closer - log *slog.Logger - iters []*ebpfcommon.Iter + cfg *obi.Config + bpfObjects BpfObjects + bpfIterObjects BpfIterObjects + closers []io.Closer + log *slog.Logger + iters []*ebpfcommon.Iter } func New(cfg *obi.Config) *Tracer { @@ -45,29 +47,32 @@ func (p *Tracer) AllowPID(app.PID, uint32, *svc.Attrs) {} func (p *Tracer) BlockPID(app.PID, uint32) {} -func (p *Tracer) Load() (*ebpf.CollectionSpec, error) { - return LoadBpf() -} - -func (p *Tracer) SetupTailCalls() { -} - -func (p *Tracer) Constants() map[string]any { - m := make(map[string]any, 3) +func (p *Tracer) LoadSpecs() ([]*ebpfcommon.SpecBundle, error) { + spec, err := LoadBpf() + if err != nil { + return nil, err + } - // The eBPF side does some basic filtering of events that do not belong to - // processes which we monitor. We filter more accurately in the userspace, but - // for performance reasons we enable the PID based filtering in eBPF. - // This must match httpfltr.go, otherwise we get partial events in userspace. - if p.cfg.Discovery.BPFPidFilterOff { - m["filter_pids"] = int32(0) - } else { - m["filter_pids"] = int32(1) + iterSpec, err := LoadBpfIter() + if err != nil { + return nil, err } - m["max_transaction_time"] = uint64(p.cfg.EBPF.MaxTransactionTime.Nanoseconds()) + return []*ebpfcommon.SpecBundle{ + { + Spec: spec, + Objects: &p.bpfObjects, + Constants: p.constants(), + }, + { + Spec: iterSpec, + Objects: &p.bpfIterObjects, + Constants: p.iterConstants(), + }, + }, nil +} - // Set injection flags based on context propagation configuration +func (p *Tracer) constants() map[string]any { flags := uint32(0) if p.cfg.EBPF.ContextPropagation.HasHeaders() { flags |= 1 // k_inject_http_headers @@ -75,20 +80,32 @@ func (p *Tracer) Constants() map[string]any { if p.cfg.EBPF.ContextPropagation.HasTCP() { flags |= 2 // k_inject_tcp_options } - m["inject_flags"] = flags - m["g_bpf_debug"] = p.cfg.EBPF.BpfDebug - return m + filterPids := int32(1) + if p.cfg.Discovery.BPFPidFilterOff { + filterPids = 0 + } + + return map[string]any{ + "filter_pids": filterPids, + "max_transaction_time": uint64(p.cfg.EBPF.MaxTransactionTime.Nanoseconds()), + "inject_flags": flags, + "g_bpf_debug": p.cfg.EBPF.BpfDebug, + } +} + +func (p *Tracer) iterConstants() map[string]any { + return map[string]any{ + "g_bpf_debug": p.cfg.EBPF.BpfDebug, + } } +func (p *Tracer) SetupTailCalls() {} + func (p *Tracer) RegisterOffsets(_ *exec.FileInfo, _ *goexec.Offsets) {} func (p *Tracer) ProcessBinary(_ *exec.FileInfo) {} -func (p *Tracer) BpfObjects() any { - return &p.bpfObjects -} - func (p *Tracer) AddCloser(c ...io.Closer) { p.closers = append(p.closers, c...) } @@ -147,7 +164,7 @@ func (p *Tracer) Iters() []*ebpfcommon.Iter { return p.iters } - p.iters = []*ebpfcommon.Iter{{Program: p.bpfObjects.ObiSkIterTcp}} + p.iters = []*ebpfcommon.Iter{{Program: p.bpfIterObjects.ObiSkIterTcp}} return p.iters } @@ -178,6 +195,7 @@ func (p *Tracer) Run(ctx context.Context, _ *ebpfcommon.EBPFEventContext, _ *msg <-ctx.Done() p.bpfObjects.Close() + p.bpfIterObjects.Close() p.log.Debug("tpinjector terminated") } diff --git a/pkg/internal/ebpf/tpinjector/tpinjector_notlinux.go b/pkg/internal/ebpf/tpinjector/tpinjector_notlinux.go index 745a361048..574cadd970 100644 --- a/pkg/internal/ebpf/tpinjector/tpinjector_notlinux.go +++ b/pkg/internal/ebpf/tpinjector/tpinjector_notlinux.go @@ -28,8 +28,7 @@ type Tracer struct{} func New(_ *obi.Config) *Tracer { return nil } func (p *Tracer) AllowPID(_ app.PID, _ uint32, _ *svc.Attrs) {} func (p *Tracer) BlockPID(_ app.PID, _ uint32) {} -func (p *Tracer) Load() (*ebpf.CollectionSpec, error) { return nil, nil } -func (p *Tracer) BpfObjects() any { return nil } +func (p *Tracer) LoadSpecs() ([]*ebpfcommon.SpecBundle, error) { return nil, nil } func (p *Tracer) AddCloser(_ ...io.Closer) {} func (p *Tracer) GoProbes() map[string][]*ebpfcommon.ProbeDesc { return nil } func (p *Tracer) KProbes() map[string]ebpfcommon.ProbeDesc { return nil } @@ -46,7 +45,6 @@ func (p *Tracer) UnlinkInstrumentedLib(_ uint64) {} func (p *Tracer) AlreadyInstrumentedLib(_ uint64) bool { return false } func (p *Tracer) Run(_ context.Context, _ *ebpfcommon.EBPFEventContext, _ *msg.Queue[[]request.Span]) { } -func (p *Tracer) Constants() map[string]any { return nil } func (p *Tracer) SetupTailCalls() {} func (p *Tracer) RegisterOffsets(_ *exec.FileInfo, _ *goexec.Offsets) {} func (p *Tracer) ProcessBinary(_ *exec.FileInfo) {} diff --git a/pkg/internal/ebpf/tpinjector/tpinjector_test.go b/pkg/internal/ebpf/tpinjector/tpinjector_test.go index b90cdd1b6f..8007ddec98 100644 --- a/pkg/internal/ebpf/tpinjector/tpinjector_test.go +++ b/pkg/internal/ebpf/tpinjector/tpinjector_test.go @@ -17,85 +17,65 @@ import ( "go.opentelemetry.io/obi/pkg/obi" ) -func TestTracer_Constants_InjectFlags(t *testing.T) { +// tpinjector has two BPF specs: the main tpinjector (spec 0) and the sock iterator (spec 1). +const expectedSpecCount = 2 + +func TestTracer_Constants(t *testing.T) { tests := []struct { name string contextPropagation string + bpfPidFilterOff bool expectedInjectFlags uint32 + expectedFilterPids int32 }{ { - name: "disabled", + name: "all disabled, filter on", contextPropagation: "disabled", - expectedInjectFlags: 0, // neither HTTP headers nor TCP options + bpfPidFilterOff: false, + expectedInjectFlags: 0, + expectedFilterPids: 1, }, { name: "headers only", contextPropagation: "headers", + bpfPidFilterOff: false, expectedInjectFlags: 1, // k_inject_http_headers + expectedFilterPids: 1, }, { name: "tcp only", contextPropagation: "tcp", + bpfPidFilterOff: false, expectedInjectFlags: 2, // k_inject_tcp_options + expectedFilterPids: 1, }, { name: "headers and tcp", contextPropagation: "headers,tcp", + bpfPidFilterOff: false, expectedInjectFlags: 3, // k_inject_http_headers | k_inject_tcp_options + expectedFilterPids: 1, }, { name: "all", contextPropagation: "all", + bpfPidFilterOff: false, expectedInjectFlags: 3, // k_inject_http_headers | k_inject_tcp_options + expectedFilterPids: 1, }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg := &obi.Config{ - EBPF: config.EBPFTracer{ - MaxTransactionTime: 10 * time.Second, - }, - } - err := cfg.EBPF.ContextPropagation.UnmarshalText([]byte(tt.contextPropagation)) - require.NoError(t, err) - - tracer := New(cfg) - constants := tracer.Constants() - - // Check that inject_flags is set correctly - injectFlags, ok := constants["inject_flags"] - assert.True(t, ok, "inject_flags should be present in constants") - assert.Equal(t, tt.expectedInjectFlags, injectFlags, "inject_flags value mismatch") - - // Verify the logic - expectedFlags := uint32(0) - if cfg.EBPF.ContextPropagation.HasHeaders() { - expectedFlags |= 1 - } - if cfg.EBPF.ContextPropagation.HasTCP() { - expectedFlags |= 2 - } - assert.Equal(t, expectedFlags, injectFlags, "inject_flags should match expected calculation") - }) - } -} - -func TestTracer_Constants_FilterPids(t *testing.T) { - tests := []struct { - name string - bpfPidFilterOff bool - expectedFilterVal int32 - }{ { - name: "filter enabled", - bpfPidFilterOff: false, - expectedFilterVal: 1, + name: "filter off", + contextPropagation: "disabled", + bpfPidFilterOff: true, + expectedInjectFlags: 0, + expectedFilterPids: 0, }, { - name: "filter disabled", - bpfPidFilterOff: true, - expectedFilterVal: 0, + name: "headers, filter off", + contextPropagation: "headers", + bpfPidFilterOff: true, + expectedInjectFlags: 1, + expectedFilterPids: 0, }, } @@ -109,13 +89,35 @@ func TestTracer_Constants_FilterPids(t *testing.T) { MaxTransactionTime: 10 * time.Second, }, } + err := cfg.EBPF.ContextPropagation.UnmarshalText([]byte(tt.contextPropagation)) + require.NoError(t, err) + + bundles, err := New(cfg).LoadSpecs() + require.NoError(t, err) + require.Len(t, bundles, expectedSpecCount, "tpinjector bundle count must match") + + // Spec 0 (tpinjector) carries the main constants. + c := bundles[0].Constants + + injectFlags, ok := c["inject_flags"] + assert.True(t, ok, "inject_flags should be present") + assert.Equal(t, tt.expectedInjectFlags, injectFlags) + + filterPids, ok := c["filter_pids"] + assert.True(t, ok, "filter_pids should be present") + assert.Equal(t, tt.expectedFilterPids, filterPids) + + _, ok = c["max_transaction_time"] + assert.True(t, ok, "max_transaction_time should be present") - tracer := New(cfg) - constants := tracer.Constants() + _, ok = c["g_bpf_debug"] + assert.True(t, ok, "g_bpf_debug should be present") - filterPids, ok := constants["filter_pids"] - assert.True(t, ok, "filter_pids should be present in constants") - assert.Equal(t, tt.expectedFilterVal, filterPids, "filter_pids value mismatch") + // Spec 1 (sock_iter) carries only the debug flag. + iterC := bundles[1].Constants + _, ok = iterC["g_bpf_debug"] + assert.True(t, ok, "iter g_bpf_debug should be present") + assert.Len(t, iterC, 1, "iter spec should have only g_bpf_debug") }) } } diff --git a/pkg/internal/ebpf/watcher/watcher.go b/pkg/internal/ebpf/watcher/watcher.go index 643c3f2820..7482345124 100644 --- a/pkg/internal/ebpf/watcher/watcher.go +++ b/pkg/internal/ebpf/watcher/watcher.go @@ -10,8 +10,6 @@ import ( "io" "log/slog" - "github.com/cilium/ebpf" - "go.opentelemetry.io/obi/pkg/appolly/app/request" "go.opentelemetry.io/obi/pkg/config" ebpfcommon "go.opentelemetry.io/obi/pkg/ebpf/common" @@ -52,18 +50,20 @@ func New(cfg *obi.Config, events chan<- Event) *Watcher { } } -func (p *Watcher) Load() (*ebpf.CollectionSpec, error) { - return LoadBpf() -} - -func (p *Watcher) Constants() map[string]any { - return map[string]any{ - "g_bpf_debug": p.cfg.EBPF.BpfDebug, +func (p *Watcher) LoadSpecs() ([]*ebpfcommon.SpecBundle, error) { + spec, err := LoadBpf() + if err != nil { + return nil, err } + return []*ebpfcommon.SpecBundle{{ + Spec: spec, + Objects: &p.bpfObjects, + Constants: p.constants(), + }}, nil } -func (p *Watcher) BpfObjects() any { - return &p.bpfObjects +func (p *Watcher) constants() map[string]any { + return map[string]any{"g_bpf_debug": p.cfg.EBPF.BpfDebug} } func (p *Watcher) AddCloser(c ...io.Closer) {