feat(bpf): userspace ring-buffer reader + decoder + per-probe attach + gone-pid guard fix#63
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>
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.
Wires the kernel birth program to userspace. Stacked on #62 — GitHub will retarget this to
masteronce #62 merges.What
backend/linux/BpfBirthReader.{h,cpp}(gated onQIFTOP_HAVE_BPF): loads the CO-RE skeleton, attaches the fexit/fentry probes, drains the ring buffer on a dedicatedstd::thread, feeds each birth to aSink. Routes libbpf logging into the verbose category.backend/linux/BirthDecode.h: the PURE wire-event →BirthKey/BirthRecorddecoder (no kernel/libbpf, always built + tested). Network-order bytes →QHostAddress(v4/v6), host-order ports, IANA proto →L4Proto, direction byte →Direction,start_boottime_ns→/procfield-22 clock ticks viasysconf(_SC_CLK_TCK). Pinned bytest_birth_decode.bpf_program__attach); the ones that take are kept, so a kernel where one traced function is renamed/inlined/non-attachable still yields births from the rest.start()succeeds if ≥1 attaches (logsattached N/M probes). We own + free the links.start_time→start_boottimecapture (the field/procfield-22 derives from — verified againstfs/proc/array.c), so the PID-reuse guard's equality check is sound.live==0) instead of rejecting it — that's exactly the short-lived process the hybrid exists to catch. Rejects only when a DIFFERENT live process holds the pid. Test flipped (goneShortLivedPidIsServed).Validation
start()returns false + inert, no crash, both unprivileged (memlock EPERM) and root (BTF missing).connect()s attributed to the right pid/comm/tuple/direction, and the short-livedcurlchild captured — the conntrack gap, closed.Next
Factory wiring (construct
BpfBirthResolverfirst, point a reader at itsonBirth,setLoaded(true)only on a successfulstart(), advertise thebirth-attributiontoken) is the follow-up PR. Contract documented in AGENTS.md §8b.