Skip to content

feat(node): start the eBPF descriptor tracker on request and refuse to start when the host cannot run it (ADR-1539) - #1993

Merged
lusoris merged 1 commit into
masterfrom
feat/node-ebpf-loader
Oct 4, 2026
Merged

lusoris merged 1 commit into
masterfrom
feat/node-ebpf-loader

Conversation

@lusoris

@lusoris lusoris commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Summary

VMAFX_EBPF_BYPASS=1 now starts the node's eBPF descriptor tracker, and a host that cannot run it stops the node with every reason. Before, the loader under cmd/vmafx-node/bpf was never started, VMAFX_EBPF_MOUNT_PREFIX was read by no code, and the tree held a stub whose Start always failed (docs audit 2026-10-03, defect 35).

  • Wiring: the tracker starts in fx OnStart between the storage layer and the controller client and stops after the client drained. It watches VMAFX_EBPF_MOUNT_PREFIX; the node refuses to start unless storage mounts under that prefix (VMAFX_STORAGE_MODE resolving to mount, VMAFX_STORAGE_MOUNT_ROOT inside the prefix), since otherwise it would observe nothing.
  • Fail closed: bpf.Preflight lists every failing requirement before any BPF syscall: kernel older than 5.15, no /sys/kernel/btf/vmlinux, syscall tracepoints not visible in tracefs, no CAP_BPF+CAP_PERFMON or CAP_SYS_ADMIN. A relative or over-long prefix is refused instead of truncated. Off little-endian Linux the request is refused.
  • Real object: the bpf2go output (pinned v0.22.0, little-endian targets) is committed with a minimal vmlinux.h; the same clang regenerates it byte for byte (go generate ./cmd/vmafx-node/bpf/, which also adds the licence header).
  • Bugs found on the way: the hand-written mount_prefix_t mirror was 264 bytes against the map's 260-byte value, so the first map write would have been refused; the loader now uses bpf2go's mirrors and decodes ring events without unsafe. The descriptor cache only grew; it now prunes closed descriptors at 4096 entries.
  • Honesty: the tracker records descriptors only. ADR-0779's read bypass has no consumer (the vmaf CLI reads the mounted files itself; the mount has no VFS cache), so the docs say so, the skipping benchmark that timed two identical FUSE reads is removed, and T-NODE-EBPF-BYPASS-NO-READ-PATH-2026-10-04 tracks the gap.

Type

  • feat — new feature

Checklist

  • Commits follow Conventional Commits (the commit-msg hook enforces this).
  • make format && make lint is green locally. Go: gofmt, go vet, gosec v2.29.0 (0 issues outside generated code), commit hooks (HISS audit, dedupe, shfmt, shellcheck, markdownlint, copyright) green.
  • Unit tests pass: python3 scripts/ci/run_meson_test.py -- -C build. No libvmaf C change; Go tests below.
  • If I touched any SIMD/GPU code path, I ran /cross-backend-diff and the worst ULP is ≤ 2. No SIMD/GPU code touched.
  • If I touched a feature extractor with SIMD/GPU twins, I either updated every twin or listed the gap under "Known follow-ups" below. No extractor touched.
  • If I added a new .c / .cpp / .cu / .h / .hpp, it has the appropriate license header (see CONTRIBUTING.md). cmd/vmafx-node/bpf/vmlinux.h carries the fork header.
  • If this is a breaking change, the commit message uses ! or BREAKING CHANGE: and the migration path is documented below. Not breaking: the tracker stays off by default.
  • If this PR adds an ADR, the ADR row lives in docs/adr/_index_fragments/<NNNN-slug>.md and the slug is appended to docs/adr/_index_fragments/_order.txt — do not edit docs/adr/README.md directly (regenerated by scripts/docs/concat-adr-index.sh; see ADR-0221).

Bug-status hygiene (ADR-0165)

  • docs/state.md updated in this PR: T-NODE-EBPF-LOADER-NOT-WIRED-2026-10-04 opened and closed (Recently closed); T-NODE-EBPF-BYPASS-NO-READ-PATH-2026-10-04 opened (Open bugs).

Netflix golden-data gate (ADR-0024)

  • I did not modify any assertAlmostEqual(...) score in the Netflix golden Python tests.
  • If I believe a golden value must change, I have explained why below AND pinged @lusoris for a CODEOWNERS exception. No golden value changes.

Deep-dive deliverables (ADR-0108)

  • Research digest — no digest needed: Research-0733 holds the eBPF research; this PR wires the existing loader and records in ADR-1539 why its read bypass has no consumer.
  • Decision matrix — ADR-1539 ## Alternatives considered.
  • AGENTS.md invariant note — cmd/vmafx-node/AGENTS.md invariant 15.
  • Reproducer / smoke-test command — below.
  • CHANGELOG fragment — changelog.d/added/node-ebpf-tracker.md.
  • Rebase note — docs/rebase-notes.md, "vmafx-node starts the eBPF descriptor tracker on request (ADR-1539, 2026-10-04)".

Reproducer

export CGO_LDFLAGS="-L$PWD/core/build-cpu/src -lvmaf -lm" LD_LIBRARY_PATH=$PWD/core/build-cpu/src
go test -race -count=1 ./cmd/vmafx-node/...
go build -o /tmp/vmafx-node ./cmd/vmafx-node && mkdir -p /tmp/rclone-mount/jobs && \
  VMAFX_EBPF_BYPASS=1 VMAFX_EBPF_MOUNT_PREFIX=/tmp/rclone-mount VMAFX_STORAGE_MODE=mount \
  VMAFX_STORAGE_MOUNT_ROOT=/tmp/rclone-mount/jobs VMAFX_GRPC_LISTEN=127.0.0.1:0 /tmp/vmafx-node
# unprivileged: exits 1 with the tracefs and CAP_BPF/CAP_PERFMON reasons

Evidence (2026-10-04, kernel 7.2.8, unprivileged): node packages ok under -race. The node binary with VMAFX_EBPF_BYPASS=1 exits 1: "VMAFX_EBPF_BYPASS is set but the eBPF tracker cannot start: ebpf: syscall tracepoints not visible in tracefs (mount /sys/kernel/tracing): ... permission denied ... ebpf: process lacks CAP_BPF and CAP_PERFMON (or CAP_SYS_ADMIN); CapEff=0x0". With the provider returning nil, TestEBPFStartFailsClosed and TestEBPFRefusedWithHTTPServe fail; TestEmbeddedObjectMatchesMirrors checks the object's programs, maps and struct sizes (it would catch the 264/260 mismatch). go generate reproduces the committed object byte for byte (clang 23.1.1).

Known follow-ups

  • Not verified: a privileged load and attach (needs CAP_BPF; no privileged host was available to this change).
  • T-NODE-EBPF-BYPASS-NO-READ-PATH-2026-10-04: a real read bypass needs a cached mount and a scorer input that reads the cache file.
  • The BPF program's source is SPDX EUPL-1.2 while it declares "Dual BSD/GPL" to the kernel (pre-existing); a maintainer licence decision.

…o start when the host cannot run it (ADR-1539) (#1993)

* feat(node): start the eBPF descriptor tracker on request and refuse to start when the host cannot run it (ADR-1539)

The eBPF loader under cmd/vmafx-node/bpf was never started,
VMAFX_EBPF_MOUNT_PREFIX was read by no code, and the tree held a stub whose
Start always failed (docs audit 2026-10-03, defect 35).

VMAFX_EBPF_BYPASS=1 now starts the tracker between the storage layer and the
controller client, watching VMAFX_EBPF_MOUNT_PREFIX. The node refuses to
start when storage does not mount under the prefix, and when bpf.Preflight
fails, listing every reason: kernel older than 5.15, no kernel BTF, syscall
tracepoints not visible in tracefs, no CAP_BPF+CAP_PERFMON or
CAP_SYS_ADMIN. A relative or over-long prefix is refused instead of
truncated; off Linux the request is refused.

The bpf2go object (pinned v0.22.0, both endiannesses) is committed with a
minimal vmlinux.h and regenerates byte for byte with the same clang. The
loader now uses bpf2go's struct mirrors: the hand-written mount_prefix_t
mirror was 264 bytes against the map's 260-byte value. Ring events decode
without unsafe, and the descriptor cache prunes closed entries at 4096.

The tracker records descriptors only: no read path uses them, because the
vmaf CLI reads the mounted files itself and the mount runs without a VFS
cache. The documentation says so, and the skipping benchmark that measured
two identical FUSE reads is removed.

* docs: regenerate the indexes and the citation map after rebasing
@lusoris
lusoris force-pushed the feat/node-ebpf-loader branch from 5db0c2c to 8cf6d61 Compare October 4, 2026 05:11
@lusoris
lusoris merged commit 8cf6d61 into master Oct 4, 2026
61 of 82 checks passed
@lusoris
lusoris deleted the feat/node-ebpf-loader branch October 4, 2026 05:12
lusoris added a commit that referenced this pull request Oct 4, 2026
… loader (#2001)

* fix(docs): point the platform figure's eBPF evidence at the generated loader

#1993 removed cmd/vmafx-node/bpf/rclone_bypass_stub.go, which the
phase4b-platform figure cited as evidence for rcloneBypassObjects, so
`make docs-figures` (verify-all and the dedupe-gate-contract pre-push
hook) failed on master. The symbol now lives in the bpf2go output
rclonebypass_bpfel.go; the figure cites that file and its JSON is
rebuilt.
@github-actions github-actions Bot added the type:feature New feature or request label Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant