feat(netprobe): move p0f encoding to userspace; fix eBPF verifier (#3425) #3514
No reviewers
Labels
No labels
1week
2weeks
Failed compliance check
IP cameras
NATS
Possible security concern
Review effort 1/5
Review effort 2/5
Review effort 3/5
Review effort 4/5
Review effort 5/5
UI
aardvark
accessibility
amd64
api
arm64
auth
back-end
bgp
blog
bug
build
checkers
ci-cd
cleanup
cnpg
codex
core
dependencies
device-management
documentation
duplicate
dusk
ebpf
enhancement
eta 1d
eta 1hr
eta 3d
eta 3hr
feature
fieldsurvey
github_actions
go
good first issue
help wanted
invalid
javascript
k8s
log-collector
mapper
mtr
needs-triage
netflow
network-sweep
observability
oracle
otel
plug-in
proton
python
question
reddit
redhat
research
rperf
rperf-checker
rust
sdk
security
serviceradar-agent
serviceradar-agent-gateway
serviceradar-web
serviceradar-web-ng
siem
snmp
sysmon
topology
ubiquiti
wasm
wontfix
zen-engine
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
carverauto/serviceradar!3514
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/netprobe-p0f-userspace-ebpf-verifier"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Part of the native add-on rollout (#3425). The netprobe eBPF object could not load on the target kernel: the TCP-SYN / p0f path first exceeded the BPF verifier's 1M-instruction complexity limit (the
p0f::encodeoption/quirk string builder), and once that was removed it hit the 512-byte BPF stack limit. This moves p0f signature encoding out of the eBPF program into userspace and restructures the eBPF datapath so every program verifies cheaply, while making p0f matching actually work.eBPF (
rust/netprobe/ebpf/src/lib.rs)p0f::encode,emit_p0f_signature, theP0fRecordstruct and thep0f_signaturesring buffer. The TC-SYN path now only parses the SYN into a rawTcpSynSignatureRecordand emits it totcp_syn_signatures. The record gainssource_endpoint+df/id+/id-IP-level quirk bits and a deterministic#[repr(C)]104-byte layout.netprobe_tc_syn_signature) so it verifies with its own fresh 512-byte stack; the entry classifiers do flow accounting thenbpf_tail_call. The tail call lives in the entry program because the verifier rejectsbpf_tail_callinside bpf-to-bpf subprograms.flow_tableupdate out of the parse chain, de-inline the IPv4/IPv6 parse + SYN-header helpers, and compare 16-byte addresses as a single big-endianu128instead of a byte loop. The byte loop unrolled into per-byte stack spills (~392-byte frames); theu128compare droppedparse_ipv6_flow_key392→64 andrecord_flow_pid480→168 bytes.Userspace
rust/netprobe/src/p0f_encode.rs: theno_std/no-alloc p0f encoder moved from the eBPF crate, extended to renderdf/id+/id-quirks.fingerprint.rsreads the rawtcp_syn_signaturesrecords, builds the p0f signature string in userspace, and matches the bundled corpus exactly as before (P0fSignatureRuntimerepointed fromp0f_signatures→tcp_syn_signatures).df/id+quirks make real SYNs match the bundled corpus — the old eBPF-encoded path emitted no IP-level quirks and therefore matched nothing.Verification
Built hermetically (RBE) and validated on a kernel-6.8 box (
agent-sr-test-pve04, amd64):flow_table; p0f fingerprint events flow via the userspace path (netprobe_events_emitted_total{stream="fingerprint"}increments).Unit tests updated for the raw-record format;
bazel test //rust/netprobe:netprobe_testpasses.🤖 Generated with Claude Code
lgtm