feat(bpf): wire eBPF birth into the resolver chain (BpfBirthSource)#64
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The factory integration that lights up birth attribution end-to-end. Stacked on #62 (retargets to
masteron merge).What
backend/linux/BpfBirthSource— aProcessResolveradapter owning theBpfBirthReaderand aBpfBirthResolver: feeds the reader's births into the resolver and gatessetLoaded(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— constructsBpfBirthSourceFIRST in the composite (underBACKEND_LINUX && QIFTOP_HAVE_BPF, gated oncfg.processAttribution), so birth wins for flows it saw and sock_diag attributes the rest. Both the agent (InterfacesServicemerges resolver caps) and the in-processConntrackMonitorbuild via this one factory, sobirth-attribution+process-attribution[-wire]flow to the wire automatically — no service changes, nousingAgentassumption.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
createProcessResolver()and ran a harness:resolvePidattributed a live connection to the correct pid via birth.Next
hybrid-sandbox— systemdCAP_BPF+ verifySystemCallFilterallowsbpf(), 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).