Introduction to Benchmarking skb Drop Reason Tracing
The Linux socket buffer (skb) carries a drop_reason field that is set when a packet is discarded by the networking stack. Tracing this field lets operators see why packets are dropped (congestion, ACL, RPF failure, LRO overflow, etc.) without touching the data path. The mechanism relies on eBPF programs attached to kernel probes that fire when an skb is freed or consumed, read the drop reason via bpf_skb_get_drop_reason(), and record it in a userspace‑visible map.
Production observability must stay low‑overhead. Adding a tracing probe consumes CPU cycles, memory bandwidth, and can cause contention on shared resources (per‑CPU maps, ring buffers). Benchmarking across kernel releases quantifies that overhead so teams can:
- Verify the tracing cost fits a latency budget (e.g., < 2 µs per packet at 10 Mpps).
- Spot regressions caused by helper changes or symbol renames.
- Pick the probe type (kprobe, tracepoint, fentry/fexit) that gives the best visibility‑overhead trade‑off for a given kernel version.
Kernel Versions and Probe Choice
Kernel Overview
| Kernel | Release | Notable skb‑tracing changes |
|---|---|---|
| 5.15 | LTS (Oct 2021) | First stable inclusion of bpf_skb_get_drop_reason() and bpf_skb_set_drop_reason(); tracepoint kfree_skb present. |
| 6.1 | LTS (Jan 2023) | Helper signatures unchanged; added BPF_CORE_READ support for direct skb->drop_reason access; tracepoint kfree_skb unchanged. |
| 6.6 | LTS (Oct 2023) | No new drop‑reason helpers; improved symbol visibility for __kfree_skb due to reduced inline‑attribute churn; tracepoint kfree_skb still stable. |
All three ship with CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, and CONFIG_DEBUG_INFO_BPF=y in typical distro configs, enabling libbpf‑based tracing.
Probe Options
| Probe Type | Attachment Point | Visibility | Overhead Characteristics |
|---|---|---|---|
| Tracepoint | tracepoint:kfree_skb (defined in include/trace/events/skb.h) | Fires on every kfree_skb() call, including those from consume_skb() and __kfree_skb() | Very low; uses existing tracepoint infrastructure, no symbol resolution needed. |
| Kprobe | kprobe:__kfree_skb (or kprobe:kfree_skb) | Fires on the exact function entry; can be paired with fexit for exit‑side data | Slightly higher due to dynamic symbol lookup and possible JIT compilation overhead. |
| Fentry/Fexit | fentry:__kfree_skb / fexit:__kfree_skb (requires CONFIG_TRACING=y and CONFIG_BPF_TRACING=y) | Similar to kprobe but uses the newer BPF trampoline mechanism, reducing context‑save overhead. | Lowest among dynamic probes when the kernel supports BPF trampoline (5.5+). |
| Kretprobe | kretprobe:__kfree_skb | Captures the return value (success/failure) after the free operation. | Overhead comparable to kprobe; useful if you need to know whether the skb was actually freed. |
Choosing a Probe for Benchmarking
- Tracepoint – preferred when the tracepoint exists and is stable across kernels (it does for
kfree_skb). It eliminates symbol‑resolution variance and yields deterministic overhead. - Fentry/Fexit – chosen when tracepoint granularity is insufficient (e.g., you need to capture arguments before they are overwritten) and the kernel version supports BPF trampoline (≥ 5.5).
- Kprobe – serves as a fallback on kernels lacking fentry support or when you need to probe a function that lacks a tracepoint (rare for skb free paths).
For the benchmark we compare:
- Tracepoint on
kfree_skb. - Fentry on
__kfree_skb(when available). - Kprobe on
__kfree_skb(as a baseline).
Helper Availability and Its Impact
Core Helpers
bpf_skb_get_drop_reason(struct sk_buff *skb)– returns the enum stored inskb->drop_reason.bpf_skb_set_drop_reason(struct sk_buff *skb, u32 reason)– mainly for testing; not needed for passive observation.
Frequently used alongside:
bpf_probe_read_kernel()– safe field reads whenBPF_CORE_READisn’t usable.bpf_get_smp_processor_id()– per‑CPU map indexing.bpf_ringbuf_reserve()/bpf_ringbuf_submit()– low‑latency event output.bpf_map_lookup_elem()/bpf_map_update_elem()– histogram aggregation.
Helper Availability Across Kernels
| Kernel | Helper Status |
|---|---|
| 5.15 | bpf_skb_get_drop_reason() and bpf_skb_set_drop_reason() present (since 5.5). bpf_probe_read_kernel(), bpf_get_smp_processor_id(), ringbuf helpers present. BPF_CORE_READ available but limited to simple field access; requires vmlinux.h from bpftool gen. |
| 6.1 | No change to drop‑reason helper signatures. Enhanced BPF_CORE_READ understands bitfield packing (future‑proof). Added offload helpers (bpf_skb_get_tx_hash()) – irrelevant here. Ringbuf reservation hints improved. |
| 6.6 | Drop‑reason helpers unchanged. bpf_probe_read_kernel() and bpf_probe_read_user() get inline variants when CONFIG_BPF_LLVM_INLINE=y. Introduced bpf_spin_lock()/unlock() (optional, higher overhead). Ringbuf and map helpers remain stable. |
Overall, the helper set relevant to drop‑reason tracing is stable from 5.15 → 6.6; performance differences arise from probe choice and kernel internals (inlining, layout changes), not helper availability.
Symbol Stability and Its Effect
What Makes a Symbol Stable?
A symbol is stable for tracing when its name persists across builds and its address can be reliably resolved via /proc/kallsyms or BPF CO‑RE. Instability affects:
- Kprobe/Fentry attachment – requires the exact symbol name; a rename forces re‑attachment or failure.
- Tracepoint usage – immune to symbol changes because tracepoints are identified by a static string in the tracepoint definition.
- BPF CO‑RE – relies on struct field offsets; if a struct layout changes, regenerate
vmlinux.h, but the probe itself does not break.
Symbol Stability per Kernel
-
5.15 –
__kfree_skbis notstatic inline; it appears as an exported symbol inkallsyms. Kprobe/fentry on__kfree_skbworks reliably. The tracepointkfree_skb(intrace/events/skb.h) has been unchanged since 4.14. No known renames in the skb free path. -
6.1 –
__kfree_skbreceived__always_inlineunder certain configs (e.g.,CONFIG_OPTIMIZE_INLINING=y). When inlined, the separate symbol may disappear fromkallsyms, breaking a raw kprobe. The tracepointkfree_skbremains unaffected because it sits at the call site. Fentry/Fexit probes, which use the BPF trampoline, can still attach to the inlined function via the compiler‑generated call site, making them more resilient than raw kprobes. -
6.6 – Inlining of
__kfree_skbis now the default in most distro builds. The tracepointkfree_skbis still present and stable. Relying on a kprobe is risky unless the kernel is built withCONFIG_DEBUG_INFO_BPF=yandCONFIG_KPROBES_SANITIZE=y(which forces the function out‑of‑line for debugging). Fentry/Fexit remain the recommended dynamic probe choice for 6.6 when a tracepoint does not provide the needed granularity.
Benchmarking Methodology and Tools
Tools
- Traffic Generator –
pktgen(built‑in) ortrexfor controllable UDP streams (1–10 Mpps, 64‑byte packets). - Latency Measurement –
cyclictest(baseline) andhwlatdetectorto isolate jitter; per‑packet latency captured viaSO_TIMESTAMPING(SCM_TSTAMP_*). - CPU Overhead –
perf stat -e cycles,instructions,cache-references,cache-missesapplied to the tracing process; alsopidstat -p <pid> 1for context‑switch count. - eBPF Program – written with libbpf (v0.8+), compiled with
clang -target bpf -O2 -g. The program attaches to the selected probe, reads the drop reason viabpf_skb_get_drop_reason(), increments a per‑CPU histogram (struct { __u64 slots[8]; }), and optionally submits events to a ringbuf for userspace consumption. - Baseline – identical traffic flow with no eBPF program loaded (or a nop program that merely returns 0).
Procedure
- Prepare the testbed – two servers linked by a 25 GbE NIC (e.g., Mellanox ConnectX‑6) with flow control disabled, RSS enabled, and
gro=off gso=off tso=offto prevent offload‑induced masking. - Kernel configuration – ensure
CONFIG_BPF_SYSCALL=y,CONFIG_BPF_JIT=y,CONFIG_DEBUG_INFO_BPF=y,CONFIG_TRACING=y,CONFIG_BPF_TRACING=y,CONFIG_KPROBES=y,CONFIG_FTRACE=y. Verify the tracepoint exists:grep kfree_skb /sys/kernel/debug/tracing/available_events - Build the eBPF program – three variants (tracepoint, fentry, kprobe) using the same source; only the SEC() annotation changes.
- Run the benchmark – start traffic, attach the program, collect latency and CPU metrics, then detach and repeat for each probe/kernel combination.
- Analyze – compare per‑packet overhead, jitter, and CPU usage against the baseline to determine which probe offers the best trade‑off for each kernel version.
End of document.