Skip to content

feat(bpf): wire eBPF birth into the resolver chain (BpfBirthSource)#64

Merged
ebenali merged 3 commits into
feat/v04-hybrid-bpf-progfrom
feat/v04-hybrid-resolver-wire
Jun 19, 2026
Merged

feat(bpf): wire eBPF birth into the resolver chain (BpfBirthSource)#64
ebenali merged 3 commits into
feat/v04-hybrid-bpf-progfrom
feat/v04-hybrid-resolver-wire

Conversation

@ebenali

@ebenali ebenali commented Jun 19, 2026

Copy link
Copy Markdown
Member

The factory integration that lights up birth attribution end-to-end. Stacked on #62 (retargets to master on merge).

What

  • backend/linux/BpfBirthSource — a ProcessResolver adapter owning the BpfBirthReader and a BpfBirthResolver: feeds the reader's births into the resolver and gates setLoaded(true) (hence capability advertisement) on a successful attach. Skip-safe — initialize() returns false when the program can't load/attach and the factory just doesn't add it. Member order joins the reader thread before the resolver it feeds.
  • ProcessResolverFactory — constructs BpfBirthSource FIRST in the composite (under BACKEND_LINUX && QIFTOP_HAVE_BPF, gated on cfg.processAttribution), so birth wins for flows it saw and sock_diag attributes the rest. Both the agent (InterfacesService merges resolver caps) and the in-process ConntrackMonitor build via this one factory, so birth-attribution + process-attribution[-wire] flow to the wire automatically — no service changes, no usingAgent assumption.
  • BpfBirthResolver — periodic TTL prune (every 4096 inserts): find() only TTL-filters (it's const), so without this, unmatched births would pile to the hard cap and clear-on-overflow would drop FRESH births on churny hosts. Keeps the live set well below the cap as a routine matter.

Validation

  • Unit: full build + 44/44 tests + clang-tidy gate clean (HAVE_BPF on); skip-safe off-build unaffected.
  • End-to-end on Vagrant Ubuntu 6.8 (real BTF kernel) — built createProcessResolver() and ran a harness:
    bpf birth: attached 4/4 probes; draining ring buffer
    ProcessResolverFactory: BpfBirthSource added (eBPF birth, first)
    composite caps = process-attribution, birth-attribution, container-attribution, container-chain, netns-scan
    birth-attribution advertised: YES
    resolvePid(our flow ...:53138 -> 1.1.1.1:80) = 3604 (our pid=3604)   exit 0
    
    The factory adds birth first, advertises the token, and resolvePid attributed a live connection to the correct pid via birth.

Next

hybrid-sandbox — systemd CAP_BPF + verify SystemCallFilter allows bpf(), validated via the systemd-dbus VM runner; then the Tier-2 VM test (short-lived container proc attributed WITH birth where conntrack-only gives pid=0).

ebenali and others added 3 commits June 19, 2026 13:45
…ard fix

Wires the kernel side of the birth+conntrack hybrid to userspace:

- backend/linux/BpfBirthReader.{h,cpp} (gated on QIFTOP_HAVE_BPF): loads the
  CO-RE skeleton (birth.skel.h), attaches the fexit/fentry probes, and drains
  the ring buffer on a dedicated std::thread, feeding each birth to a Sink
  callback. Skip-safe — start() returns false and stays inert on any kernel
  without BTF / trampolines / CAP_BPF, so the chain runs conntrack-only. Routes
  libbpf logging into the verbose category.
- backend/linux/BirthDecode.h: the PURE wire-event → BirthKey/BirthRecord
  decoder (no kernel/libbpf, so it's always built + tested). Network-order
  bytes → QHostAddress (v4/v6), host-order ports, IANA proto → L4Proto,
  direction byte → Direction, and start_boottime_ns → /proc field-22 clock
  ticks via sysconf(_SC_CLK_TCK). test_birth_decode pins the byte-order/units
  contract with hand-built events.
- CMake: backend_linux gains BpfBirthReader.cpp + links libbpf (gated).

Correctness fix to the merged BpfBirthResolver PID-reuse guard: it rejected
when live==0 (pid gone), which would reject exactly the short-lived processes
the hybrid exists to capture (they've exited by the time the conntrack flow
resolves). Now it serves a gone pid and rejects ONLY when a DIFFERENT live
process holds the pid (live!=0 && live!=captured). Test flipped accordingly
(missingPidStarttimeIsRejected → goneShortLivedPidIsServed).

Also fixes the captured starttime field (start_time → start_boottime, the one
/proc field 22 derives from) so the guard's equality check is sound.

AGENTS.md §8b documents the reader, the decoder units contract, and the
gone-pid guard semantics. Validated: full build + 44/44 tests + clang-tidy
gate clean (HAVE_BPF on, toolchain present); skip-safe off-build verified.
Factory wiring (construct resolver first, point reader at it, advertise the
birth-attribution token) is the next PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Attach each eBPF program individually (bpf_program__attach over
bpf_object__for_each_program) and keep the ones that take, instead of the
skeleton's all-or-nothing qiftop_birth__attach. A kernel where one traced
function is renamed/inlined/non-attachable (or hits the trampoline limit)
still yields birth attribution from the probes that DID attach; start()
succeeds when >=1 probe attaches and logs "attached N/M probes". We own the
returned bpf_links and destroy them in stop().

Load stays whole-object on purpose: CO-RE relocation + verification is
per-object, so if load fails nothing is attachable anyway. Still fully
skip-safe — zero probes attached tears down and returns false (conntrack-only).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the factory integration that lights up birth attribution end-to-end:

- backend/linux/BpfBirthSource: a ProcessResolver adapter that owns the
  BpfBirthReader AND a BpfBirthResolver, feeds the reader's births into the
  resolver, and gates setLoaded(true) (hence its capability advertisement) on
  a SUCCESSFUL program attach. Skip-safe: initialize() returns false when the
  program can't load/attach (no BTF / trampolines / CAP_BPF) and the factory
  simply doesn't add it. Member order ensures the reader thread is joined
  before the resolver it feeds is torn down.

- ProcessResolverFactory: construct BpfBirthSource FIRST in the composite
  (under BACKEND_LINUX && QIFTOP_HAVE_BPF, gated on cfg.processAttribution),
  so birth wins for flows it saw and sock_diag attributes the rest. Because
  both the agent (InterfacesService merges resolver caps) and the in-process
  ConntrackMonitor build via this one factory, the birth-attribution +
  process-attribution[-wire] tokens flow to the wire automatically — no
  service changes, no usingAgent assumption.

- BpfBirthResolver: periodic TTL prune (every 4096 inserts) so unmatched
  births don't pile to the hard cap and trigger clear-on-overflow that would
  drop FRESH births on churny hosts. find() only TTL-filters (it's const), so
  this keeps the live set well below the cap as a routine matter.

Full build + 44/44 tests + clang-tidy gate clean (HAVE_BPF on). Skip-safe
off-build unaffected. Agent-level VM validation follows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ebenali
ebenali merged commit e307d5c into feat/v04-hybrid-bpf-prog Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant