BPF Program Types
Every eBPF program carries exactly one program type (
enum bpf_prog_type), chosen by userspace at load time and frozen for the program’s life. That single number is the master key that determines everything else about the program: the context structure it is handed when it runs (a packet forXDP,struct pt_regsfor akprobe, a socket-address for aconnecthook), the set of helper functions and kfuncs it is allowed to call, the return values the kernel will honor, and the set of attach points where it may legally run. The verifier readsprog->type, looks up a per-type operations table, and validates the program against the rules for that type — so the same bytecode is accepted as anXDPprogram and rejected as akprobe, or vice-versa. As of Linux 6.12 LTS (2024-11-17) the kernel defines 32 concrete program types plus a reservedBPF_PROG_TYPE_UNSPECplaceholder, spanning networking, tracing, security, and the pluggable-struct mechanism behindsched_ext(enum bpf_prog_type, v6.12bpf.h). This note is the dispatcher: it explains how the type system works, then routes you to the per-type sibling notes.
This is the §5 overview of the Linux eBPF MOC. Where this note explains the taxonomy, the per-type leaves — XDP (eXpress Data Path), kprobe and uprobe BPF Programs, BPF-LSM (Security Hooks), struct_ops and sched_ext, cgroup BPF Programs — explain each family’s mechanism in depth.
Mental Model — One Number Specializes the Whole Program
The right way to think about a BPF program type is as a contract template. When you compile a BPF program you write a function int prog(void *ctx), but void *ctx is a lie of convenience — the real type of ctx is fixed by the program type. A networking program’s context is really a struct __sk_buff or struct xdp_md; a tracing program’s context is a register snapshot; a security program’s context is the arguments of an LSM hook. The program type is the value that tells the kernel which of these the ctx pointer actually points at, and therefore which fields are readable, which helpers make sense, and what a “successful” return code means.
flowchart TD LOAD["bpf() BPF_PROG_LOAD<br/>attr.prog_type = T<br/>attr.expected_attach_type = A"] OPS["bpf_verifier_ops[T]<br/>(per-type table from bpf_types.h)"] CTX["is_valid_access(off,size)<br/>which ctx fields are legal"] HELP["allowed helpers + kfuncs<br/>(per-type helper allowlist)"] RET["allowed return values<br/>(drop/pass/redirect, allow/deny...)"] ATTACH["attach surface<br/>(where it may run)"] LOAD --> OPS OPS --> CTX OPS --> HELP OPS --> RET LOAD -->|expected_attach_type| ATTACH CTX --> VERDICT{"verifier<br/>accept?"} HELP --> VERDICT RET --> VERDICT VERDICT -->|yes| JIT["JIT + attachable"] VERDICT -->|no| REJECT["EACCES / EINVAL"]
How one prog_type value (T) fans out into the four things it controls. What it shows: at BPF_PROG_LOAD the kernel indexes a per-type operations table bpf_verifier_ops[T] (built by an X-macro over bpf_types.h); that table supplies the is_valid_access callback (which context offsets are legal), the helper/kfunc allowlist, and the return-value rules, while the separate expected_attach_type narrows where the program may attach. The insight to take: the program type is not a label the kernel checks once and forgets — it parameterizes the verifier itself. The exact same instructions are legal or illegal depending on T, because T selects a different rulebook. Learn the four axes — context, helpers, return values, attach surface — and you can read any program type’s behavior off its name.
Mechanical Walk-through — How the Type Specializes the Verifier
The specialization is concrete, not conceptual; it lives in two arrays indexed by prog->type. In kernel/bpf/verifier.c the kernel builds a table:
static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) \
[_id] = & _name ## _verifier_ops,
#include <linux/bpf_types.h>
};The X-macro expands one row per program type listed in include/linux/bpf_types.h, e.g. BPF_PROG_TYPE(BPF_PROG_TYPE_XDP, xdp, struct xdp_md, struct xdp_buff) produces [BPF_PROG_TYPE_XDP] = &xdp_verifier_ops and records that the program-visible context type is struct xdp_md while the in-kernel type is struct xdp_buff (bpf_types.h, v6.12). When the verifier starts it does env->ops = bpf_verifier_ops[env->prog->type] (verifier.c, v6.12). From that point on, every context-relative memory access — ctx->field — is routed through env->ops->is_valid_access(off, size, type, ...), the per-type gatekeeper that decides whether reading or writing offset off for size bytes is permitted, and what register type results. This is why an XDP program may read ctx->data and ctx->data_end (the packet boundary pointers) while a kprobe program reading the same offset is rejected: the two types install different is_valid_access callbacks.
The second axis is the helper allowlist. Every helper exposed to BPF declares which program types may call it (via a *_func_proto lookup keyed on program type). Calling bpf_get_current_task() from a tracing program is fine; calling it from a SOCKET_FILTER program returns “unknown func” from the verifier because that type’s get_func_proto does not resolve it. The same gate covers kfuncs (kernel functions exposed by BTF id), which register an allowed-program-type set when they are defined. The result is that the “standard library” visible to a program is a function of its type — see BPF Helper Functions and BPF Kernel Functions (kfuncs).
The third axis is return values. After the verifier finishes the path walk it checks the value left in R0 (the return register) against the type’s allowed range. Networking types use a small enum of verdicts — XDP_DROP/XDP_PASS/XDP_TX/XDP_REDIRECT for XDP, TC_ACT_* for tc/sched_cls; cgroup hooks typically use 0 = deny / 1 = allow; LSM programs return 0 to allow or a negative errno to deny. A program that can return a value outside its type’s permitted set is rejected at load.
The fourth axis is the attach surface, and here a second field matters: expected_attach_type. The program type chooses the family of hook; expected_attach_type (an enum bpf_attach_type, set in the BPF_PROG_LOAD attributes) narrows it to a specific hook within the family. The clearest example is BPF_PROG_TYPE_CGROUP_SOCK_ADDR: one program type, but the expected_attach_type distinguishes BPF_CGROUP_INET4_CONNECT (rewrite the destination of an IPv4 connect) from BPF_CGROUP_UDP6_SENDMSG (rewrite a UDP6 sendmsg target) and a dozen others. The attach type also feeds back into verification — for CGROUP_SOCK_ADDR it controls which context fields are writable — so type and attach-type together form the contract. As of v6.12 the enum bpf_attach_type holds 57 attach types (v6.12 bpf.h).
The counts are read directly from the v6.12 header: enum bpf_prog_type runs from BPF_PROG_TYPE_UNSPEC (a reserved placeholder, index 0) through BPF_PROG_TYPE_NETFILTER (index 32, the last real entry before the __MAX_BPF_PROG_TYPE sentinel) — 32 concrete types; enum bpf_attach_type runs through BPF_TRACE_KPROBE_SESSION, the last entry before __MAX_BPF_ATTACH_TYPE — 57 attach types (v6.12 bpf.h).
The Major Families
The 32 types cluster into four functional families plus a handful of specialized networking variants. The names below are the exact enum bpf_prog_type identifiers from v6.12.
Networking
These programs see packets or sockets and return forwarding/filtering verdicts.
BPF_PROG_TYPE_XDP— runs in the NIC driver’s receive path before ansk_buffis even allocated, onstruct xdp_md. The fastest hook in the kernel; used for DDoS drop and L4 load-balancing. See XDP (eXpress Data Path) (existing note: XDP Express Data Path).BPF_PROG_TYPE_SCHED_CLSandBPF_PROG_TYPE_SCHED_ACT— thetc(traffic control) classifier and action hooks, onstruct __sk_buff, running aftersk_buffallocation on both ingress and egress.SCHED_CLSis the workhorse behindcls_bpf/tcx. See tc and cls_bpf (Traffic Control Hooks).BPF_PROG_TYPE_SOCKET_FILTER— the oldest extended type, a direct descendant of classic BPF (SO_ATTACH_BPFon a socket); filters packets delivered to a socket. See Classic BPF vs Extended BPF.BPF_PROG_TYPE_SK_SKB,BPF_PROG_TYPE_SK_MSG,BPF_PROG_TYPE_SOCK_OPS,BPF_PROG_TYPE_SK_REUSEPORT,BPF_PROG_TYPE_SK_LOOKUP— the socket-layer family that powerssockmap/sockhashredirection (SK_SKB/SK_MSG), TCP option/event hooks (SOCK_OPS),SO_REUSEPORTsocket selection (SK_REUSEPORT), and listener selection (SK_LOOKUP).BPF_PROG_TYPE_CGROUP_SKB,BPF_PROG_TYPE_CGROUP_SOCK,BPF_PROG_TYPE_CGROUP_SOCK_ADDR,BPF_PROG_TYPE_CGROUP_SOCKOPT— per-cgroup network policy: packet filtering, socket-create hooks,bind/connect/sendmsgaddress hooks, andgetsockopt/setsockoptinterception. These attach to a cgroup rather than a device — see cgroup BPF Programs for the full treatment.BPF_PROG_TYPE_FLOW_DISSECTOR,BPF_PROG_TYPE_LWT_IN/LWT_OUT/LWT_XMIT/LWT_SEG6LOCAL,BPF_PROG_TYPE_NETFILTER— flow dissection, lightweight-tunnel encap/decap, IPv6 Segment Routing, and (since 6.4) programmable netfilter hooks.
Tracing and Observability
These programs fire on kernel/userspace execution events and read (mostly) read-only context.
BPF_PROG_TYPE_KPROBE— attaches to a kprobe or uprobe; context isstruct pt_regs(a register snapshot at the probe site). Covers dynamic kernel-function and userspace-function tracing. See kprobe and uprobe BPF Programs.BPF_PROG_TYPE_TRACEPOINTandBPF_PROG_TYPE_RAW_TRACEPOINT(plusRAW_TRACEPOINT_WRITABLE) — attach to the kernel’s static tracepoints;TRACEPOINTgets a stable, pre-formatted argument struct, whileRAW_TRACEPOINTgets the raw arguments for lower overhead. See Tracepoint BPF Programs.BPF_PROG_TYPE_PERF_EVENT— runs when aperfevent fires (e.g. a sampling timer or a hardware PMU counter overflow), enabling profiling and hardware-counter-driven sampling. See perf_event BPF Programs.BPF_PROG_TYPE_TRACING— the modern unified tracing type used for fentry/fexit (function entry/exit via BPF trampolines),fmod_ret(modify-return), and BTF-typediterprograms. It is BTF-id-targeted and far cheaper than a kprobe because it patches a trampoline rather than trapping. See fentry fexit and BPF Trampolines.
Security
BPF_PROG_TYPE_LSM— attaches to a Linux Security Module hook; the program returns0to allow or a negative errno to deny the operation, enforcing MAC (mandatory access control) policy in BPF. See BPF-LSM (Security Hooks).
struct_ops and Scheduling
BPF_PROG_TYPE_STRUCT_OPS— does not attach to an event at all. Instead the program implements one method of a kernel-defined operations table (struct bpf_struct_ops), and a set of such programs is registered together as a map. This is the mechanism behind pluggable TCP congestion control (struct tcp_congestion_ops) and, since 6.12,sched_ext— writing a CPU scheduler in BPF. See struct_ops and sched_ext and the existing sched_ext and BPF-Defined Schedulers.BPF_PROG_TYPE_EXT— a “freplace” extension program that replaces a global function in another BPF program (used withstruct_opsand program extension).
Other
BPF_PROG_TYPE_CGROUP_DEVICE— device-access control attached to a cgroup; replaces the legacy devices controller (see cgroup BPF Programs).BPF_PROG_TYPE_CGROUP_SYSCTL— interceptssysctlreads/writes for tasks in a cgroup.BPF_PROG_TYPE_LIRC_MODE2— decodes infrared remote-control signals.BPF_PROG_TYPE_SYSCALL— a special type that can itself issue a subset ofbpf()commands, used by the “light skeleton” / loader programs to set up other BPF objects from within the kernel.
The Program-Type ↔ Attach-Type Relationship
A frequent source of confusion is the two-level naming: enum bpf_prog_type (set in attr.prog_type) and enum bpf_attach_type (set in attr.expected_attach_type at load, and again at attach). The relationship is one program type to many attach types. libbpf’s program-types table makes this explicit by mapping ELF section names to the pair, e.g. for BPF_PROG_TYPE_CGROUP_SOCK_ADDR (libbpf program_types.rst, v6.12):
SEC("cgroup/connect4") -> prog_type=CGROUP_SOCK_ADDR, attach=BPF_CGROUP_INET4_CONNECT
SEC("cgroup/connect6") -> prog_type=CGROUP_SOCK_ADDR, attach=BPF_CGROUP_INET6_CONNECT
SEC("cgroup/sendmsg4") -> prog_type=CGROUP_SOCK_ADDR, attach=BPF_CGROUP_UDP4_SENDMSG
SEC("cgroup/bind4") -> prog_type=CGROUP_SOCK_ADDR, attach=BPF_CGROUP_INET4_BIND
In practice you almost never set these fields by hand. libbpf derives both from the ELF section name in your SEC("...") annotation, looking them up in this table at load time. So SEC("xdp") selects BPF_PROG_TYPE_XDP, SEC("kprobe/vfs_read") selects BPF_PROG_TYPE_KPROBE with the attach target vfs_read, and SEC("lsm/file_open") selects BPF_PROG_TYPE_LSM targeting the file_open hook by BTF id. The section-name convention is the user-facing program-type interface; the integer enums are the kernel-facing one.
Configuration / Code — Reading and Setting the Type
Concretely, the type travels through the bpf() syscall in the BPF_PROG_LOAD attribute block (v6.12 bpf.h):
union bpf_attr {
struct { /* BPF_PROG_LOAD */
__u32 prog_type; /* one of enum bpf_prog_type */
__u32 insn_cnt;
__aligned_u64 insns; /* the bytecode */
...
__u32 expected_attach_type; /* one of enum bpf_attach_type */
__u32 attach_btf_id; /* in-kernel BTF type to attach to (fentry/LSM) */
__u32 attach_prog_fd; /* for EXT / freplace */
};
};Line by line: prog_type picks the rulebook; insns/insn_cnt are the program; expected_attach_type pre-declares which specific hook the program is destined for (so the verifier can apply attach-specific rules at load time, before any attach happens); attach_btf_id names the target function by its BTF type id for the BTF-targeted types (TRACING, LSM, STRUCT_OPS); and attach_prog_fd points at the program being extended for EXT/freplace.
With bpftool you can list a loaded program’s type:
$ bpftool prog show
27: xdp name xdp_drop tag a04f5eef06a7f555 gpl
loaded_at 2026-06-13T10:00:00+0000 uid 0
xlated 96B jited 84B memlock 4096B
$ bpftool prog show id 27 --json | jq .type
"xdp"The type field (xdp) is the enum bpf_prog_type rendered as a string. A program loaded with the wrong section name for its body — e.g. a kprobe body in a SEC("xdp") section — typically fails the verifier with an access error the moment it touches a context field that does not exist for the declared type, which is the single most common “but it compiled!” surprise for newcomers.
Failure Modes and Common Misunderstandings
“My program reads ctx->data but the verifier says invalid access.” The field exists for the type you think you’re writing but not the type the loader inferred. ctx->data/ctx->data_end exist for XDP and __sk_buff types but not for tracing types. Check the SEC() name resolved to the program type you intended (bpftool prog show after load, or libbpf’s debug log).
“Helper unknown.” The helper exists but is not on this program type’s allowlist. Helpers are gated per type; bpf_get_current_pid_tgid() is available to tracing and cgroup types but not to a bare SOCKET_FILTER. There is no runtime fallback — it is a hard load-time rejection.
“Return value rejected.” Returning a value the type does not allow (e.g. returning 2 from a cgroup hook whose verdict space is {0,1}, or an arbitrary integer from XDP that is not one of the XDP_* codes) fails verification. The allowed set is part of the type contract.
Conflating program type with attach type. CGROUP_SOCK_ADDR is one program type but covers connect, bind, sendmsg, recvmsg, getpeername, getsockname across IPv4/IPv6/Unix — twenty-odd attach types. You do not need twenty program types; you need one type and the right expected_attach_type.
Type cannot be changed after load. Because the verifier validated the bytecode against the type’s rulebook, the type is immutable. To “change type” you reload the program.
Alternatives and When to Choose Them
The program-type taxonomy is what you navigate when answering “which hook do I use?” — the Linux eBPF MOC decision framework lays out the choices. In brief: packet processing before the stack → XDP (eXpress Data Path); with the full stack and qdiscs → tc and cls_bpf (Traffic Control Hooks); low-overhead function tracing → fentry fexit and BPF Trampolines (fallback kprobe and uprobe BPF Programs); stable instrumentation → Tracepoint BPF Programs; security policy → BPF-LSM (Security Hooks); per-cgroup network/device/sysctl policy → cgroup BPF Programs; custom scheduling or congestion control → struct_ops and sched_ext. There is no “general-purpose” program type; choosing the type is choosing the integration point.
Production Notes
The type system is why a single technology underpins wildly different products without those products colliding. Cilium uses XDP, SCHED_CLS (tc/tcx), SK_MSG/SK_SKB (sockmap), and CGROUP_SOCK_ADDR (socket-based load balancing) — four different program types, each specialized by the verifier for its hook, all in one CNI (see Cilium). bpftrace and BCC use KPROBE, TRACEPOINT, PERF_EVENT, and increasingly TRACING (fentry). systemd uses CGROUP_SKB and CGROUP_DEVICE for per-unit network and device policy (see cgroup BPF Programs). Because each program’s powers are bounded by its type’s contract — verified at load — these uses coexist safely in the same kernel. The deliberate trend, documented across docs.kernel.org/bpf, is to expose new surface as kfuncs keyed to specific program types rather than minting new program types or permanent helpers, keeping the type list relatively stable while the capabilities grow (docs.kernel.org/bpf).
See Also
- Linux eBPF MOC — parent; this is the §5 dispatcher
- XDP (eXpress Data Path) (existing note: XDP Express Data Path) · tc and cls_bpf (Traffic Control Hooks) · kprobe and uprobe BPF Programs · Tracepoint BPF Programs · fentry fexit and BPF Trampolines · BPF-LSM (Security Hooks) · struct_ops and sched_ext · cgroup BPF Programs · perf_event BPF Programs — the per-type leaves this note dispatches to
- eBPF Verifier — the engine that reads
prog->typeand applies the per-type rulebook - BPF Helper Functions · BPF Kernel Functions (kfuncs) — the per-type-gated “standard library”
- The bpf() Syscall — where
prog_typeandexpected_attach_typeare set - BTF (BPF Type Format) — supplies the
attach_btf_idtarget type forTRACING/LSM/STRUCT_OPS - sched_ext and BPF-Defined Schedulers — existing note; the
STRUCT_OPS-based scheduler