eBPF Program Structure
Relevant source files
The following files were used as context for generating this wiki page:
This page documents the organization and structure of eBPF programs in the eCapture codebase, including common headers, probe type definitions, event structures, BPF maps, and helper functions. It serves as a reference for developers writing or modifying eBPF programs.
For information about generating structure offsets for specific library versions, see Structure Offset Calculation. For guidance on implementing complete capture modules, see Adding New Modules.
Directory Organization
All eBPF kernel-space programs reside in the kern/ directory. The programs follow a modular structure with shared headers and module-specific implementation files.
Sources: kern/common.h, kern/ecapture.h, kern/openssl.h, kern/tc.h
Common Headers
common.h - Global Definitions
The common.h header defines constants, macros, and global variables used across all eBPF programs.
| Definition | Purpose | Line Reference |
|---|---|---|
debug_bpf_printk() | Conditional debug output macro | kern/common.h:19-26 |
TASK_COMM_LEN | Process name length (16 bytes) | kern/common.h:28 |
MAX_DATA_SIZE_OPENSSL | TLS data buffer size (16KB) | kern/common.h:39 |
MAX_DATA_SIZE_MYSQL | MySQL query buffer size (256B) | kern/common.h:40 |
AF_INET, AF_INET6 | Address family constants | kern/common.h:49-50 |
target_pid, target_uid | Global filter variables (volatile) | kern/common.h:68-69 |
less52 | Kernel version flag (< 5.2) | kern/common.h:66 |
Key Features:
Debug Printing: The
debug_bpf_printk()macro conditionally compiles debug output based onDEBUG_PRINTflag. This avoids performance overhead in production builds.Buffer Size Constants: RFC-compliant sizes for different protocols (TLS max fragment = 16KB, database query limits).
Global Variables: Marked
volatileto indicate they are initialized from user-space via constant editors.
Sources: kern/common.h:15-86
ecapture.h - Core Infrastructure
The ecapture.h header provides the foundation for all eBPF programs, handling CO-RE (Compile Once, Run Everywhere) vs non-CO-RE compilation paths.
Conditional Compilation:
The preprocessor directive #ifndef NOCORE at kern/ecapture.h:18 determines which path is used:
- CO-RE Mode: Uses BTF (BPF Type Format) from
vmlinux.hfor kernel structure definitions. Requires kernel >= 5.2 with BTF support. - Non-CO-RE Mode: Manually includes kernel headers. Compatible with older kernels but less portable.
Filter Functions:
Two key filtering functions are defined in kern/ecapture.h:93-127:
// Check if PID/UID should be rejected
static __inline bool filter_rejects(u32 pid, u32 uid)
// Check if event passes all filters
static __always_inline bool passes_filter(struct pt_regs *ctx)These functions check against target_pid and target_uid global variables to implement selective tracing.
Sources: kern/ecapture.h:1-130
SEC() Macro and Probe Types
eBPF programs use the SEC() macro to declare their attachment type. The section name determines how the program is loaded and attached.
Probe Type Reference
| Section Name | Probe Type | Purpose | Example Function |
|---|---|---|---|
uprobe/<func> | User-space entry probe | Hook function entry | kern/openssl.h:331 |
uretprobe/<func> | User-space return probe | Hook function return | kern/openssl.h:336 |
kprobe/<func> | Kernel entry probe | Hook kernel function entry | kern/openssl.h:374 |
kretprobe/<func> | Kernel return probe | Hook kernel function return | kern/openssl.h:456 |
classifier | Traffic Control classifier | Network packet filtering | kern/tc.h:274 |
Probe Pattern: Entry/Return Pairs
Most uprobe hooks follow a two-stage pattern:
Entry Probe (probe_entry_SSL_write):
- Captures function arguments before execution
- Stores context in BPF map keyed by thread ID
- Example: kern/openssl.h:331-334
Return Probe (probe_ret_SSL_write):
- Retrieves stored context from BPF map
- Reads actual data after function completes
- Sends event to user-space
- Example: kern/openssl.h:336-339
Why This Pattern?
- Function arguments are only valid at entry time
- Return values and buffer contents are only valid at return time
- Thread ID (
bpf_get_current_pid_tgid()) correlates entry and return
Sources: kern/openssl.h:268-323, kern/openssl.h:331-351
Event Structure Definitions
Event structures define the data format sent from kernel to user-space. All event structures are packed and declared at the top of eBPF program files.
SSL Data Event
The primary event for TLS plaintext capture:
struct ssl_data_event_t {
enum ssl_data_event_type type; // kSSLRead or kSSLWrite
u64 timestamp_ns; // Nanosecond timestamp
u32 pid; // Process ID
u32 tid; // Thread ID
char data[MAX_DATA_SIZE_OPENSSL]; // Captured plaintext (16KB)
s32 data_len; // Actual data length
char comm[TASK_COMM_LEN]; // Process name
u32 fd; // File descriptor
s32 version; // TLS version
u32 bio_type; // OpenSSL BIO type
};Defined at kern/openssl.h:28-39.
Connection Event
Tracks TCP connection lifecycle:
struct connect_event_t {
unsigned __int128 saddr; // Source address (IPv4/IPv6)
unsigned __int128 daddr; // Destination address
char comm[TASK_COMM_LEN]; // Process name
u64 timestamp_ns; // Event timestamp
u64 sock; // Kernel socket pointer
u32 pid; // Process ID
u32 tid; // Thread ID
u32 fd; // File descriptor
u16 family; // AF_INET or AF_INET6
u16 sport; // Source port
u16 dport; // Destination port
u8 is_destroy; // Connection close flag
u8 pad[7]; // Padding
} __attribute__((packed));Defined at kern/openssl.h:41-55. Note the __attribute__((packed)) to eliminate padding holes.
Master Secret Event
For TLS 1.2 and 1.3 key extraction:
struct mastersecret_t {
// TLS 1.2
s32 version;
u8 client_random[SSL3_RANDOM_SIZE];
u8 master_key[MASTER_SECRET_MAX_LEN];
// TLS 1.3
u32 cipher_id;
u8 early_secret[EVP_MAX_MD_SIZE];
u8 handshake_secret[EVP_MAX_MD_SIZE];
u8 handshake_traffic_hash[EVP_MAX_MD_SIZE];
u8 client_app_traffic_secret[EVP_MAX_MD_SIZE];
u8 server_app_traffic_secret[EVP_MAX_MD_SIZE];
u8 exporter_master_secret[EVP_MAX_MD_SIZE];
};Defined at kern/openssl_masterkey.h:25-39. Contains different fields for TLS 1.2 (single master key) vs TLS 1.3 (multiple traffic secrets).
Sources: kern/openssl.h:23-55, kern/openssl_masterkey.h:21-43
BPF Map Definitions
BPF maps provide storage and communication between kernel and user-space. Maps are declared using the SEC(".maps") annotation.
Map Type Reference
PERF_EVENT_ARRAY Maps
Used for sending data to user-space. Each CPU core has a dedicated ring buffer.
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(u32));
__uint(value_size, sizeof(u32));
__uint(max_entries, 1024);
} tls_events SEC(".maps");Defined at kern/openssl.h:79-84. Similar definitions exist for:
connect_eventsat kern/openssl.h:87-92mastersecret_eventsat kern/openssl_masterkey.h:48-53skb_eventsat kern/tc.h:57-62
HASH Maps for Context Storage
Store temporary data between probe entry and return:
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u64); // Thread ID
__type(value, struct active_ssl_buf); // Function arguments
__uint(max_entries, 1024);
} active_ssl_write_args_map SEC(".maps");Defined at kern/openssl.h:104-109. The key is always bpf_get_current_pid_tgid() for thread correlation.
PERCPU_ARRAY for Large Buffers
Circumvents eBPF's 512-byte stack limit by using per-CPU map storage:
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, u32);
__type(value, struct ssl_data_event_t); // 16KB+ struct
__uint(max_entries, 1);
} data_buffer_heap SEC(".maps");Defined at kern/openssl.h:113-118. Always accessed with key 0, provides heap-allocated storage for large event structures.
LRU_HASH for Network Context
Maps network connections to process context:
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__type(key, struct net_id_t); // 5-tuple: proto, src/dst IP/port
__type(value, struct net_ctx_t); // PID, UID, comm
__uint(max_entries, 10240);
} network_map SEC(".maps");Defined at kern/tc.h:72-77. LRU (Least Recently Used) automatically evicts old entries.
Sources: kern/openssl.h:74-134, kern/tc.h:56-77, kern/openssl_masterkey.h:45-67
Helper Function Patterns
Stack Limit Workaround
eBPF programs have a 512-byte stack limit. Large structures must be allocated via maps:
Pattern Implementation:
static __inline struct ssl_data_event_t* create_ssl_data_event(u64 current_pid_tgid) {
u32 kZero = 0;
struct ssl_data_event_t* event =
bpf_map_lookup_elem(&data_buffer_heap, &kZero);
if (event == NULL) {
return NULL;
}
// Initialize fields...
return event;
}Defined at kern/openssl.h:141-158. Similar pattern in make_event() at kern/openssl_masterkey.h:71-78.
Data Reading Helpers
Reading User-Space Memory:
bpf_probe_read_user(&dest, sizeof(dest), src_ptr);Used for reading data from target application's memory space. Example at kern/openssl.h:186.
Reading Kernel Memory:
bpf_probe_read_kernel(&dest, sizeof(dest), kernel_ptr);Used for reading kernel structures like struct sock. Example at kern/openssl.h:409.
CO-RE Field Access:
#define READ_KERN(ptr) \
({ \
typeof(ptr) _val; \
__builtin_memset((void *)&_val, 0, sizeof(_val)); \
bpf_core_read((void *)&_val, sizeof(_val), &ptr); \
_val; \
})Defined at kern/tc.h:22-28. Provides portable field access in CO-RE mode.
Filtering Pattern
Standard filtering check at the beginning of probes:
if (!passes_filter(ctx)) {
return 0;
}Checks target_pid and target_uid against current process. Example at kern/openssl.h:269.
Sources: kern/openssl.h:141-191, kern/tc.h:22-28, kern/ecapture.h:93-127
Complete Program Structure Example
OpenSSL TLS Capture Program
File Structure Breakdown
| Section | Line Range | Purpose |
|---|---|---|
| License & Includes | kern/openssl.h:1-16 | Header dependencies |
| Constants & Enums | kern/openssl.h:19-26 | Event types, defaults |
| Event Structures | kern/openssl.h:28-72 | Data format definitions |
| BPF Maps | kern/openssl.h:74-134 | Storage declarations |
| Helper Functions | kern/openssl.h:136-323 | Shared logic |
| Uprobe Entry Points | kern/openssl.h:325-351 | SSL_read/write hooks |
| Connection Tracking | kern/openssl.h:354-525 | Kprobe implementations |
| SSL FD Mapping | kern/openssl.h:528-543 | SSL_set_fd hook |
Typical Program Flow
- Include Dependencies: Standard headers (
ecapture.h,tc.h) - Define Event Types: Enums and constants
- Declare Event Structures: Packed structs for user-space communication
- Declare BPF Maps: Storage for events, context, and large buffers
- Implement Helpers: Reusable logic for data extraction
- Implement Probes: Entry/return probe pairs with SEC() annotations
- Implement Kprobes: Kernel-level tracking for network context
Sources: kern/openssl.h:1-544
CO-RE vs Non-CO-RE Differences
The codebase supports both compilation modes, determined at build time.
Compilation Flags
| Mode | Flag | vmlinux.h | Kernel Headers | Portability |
|---|---|---|---|---|
| CO-RE | (default) | Required | Not needed | High - runs on any BTF kernel |
| Non-CO-RE | -D NOCORE | Not used | Required | Low - kernel-specific |
Preprocessor Conditionals
Conditional compilation at kern/ecapture.h:18-88.
Key Differences
Type Definitions:
- CO-RE: Uses BTF (BPF Type Format) from
vmlinux.hfor all kernel types - Non-CO-RE: Manually includes kernel headers and may define simplified structs (e.g.,
struct tcphdrat kern/ecapture.h:69-72)
Field Access:
- CO-RE: Uses
bpf_core_read()for portable field access that handles offset differences - Non-CO-RE: Direct pointer arithmetic with hardcoded offsets (generated by offset scripts)
Compatibility Macros:
#ifdef asm_inline
#undef asm_inline
#define asm_inline asm
#endifAt kern/ecapture.h:43-46, handles CLANG incompatibility with kernel's asm_inline macro.
Sources: kern/ecapture.h:18-88
Summary
eBPF programs in eCapture follow a consistent structure:
- Modular Headers: Common definitions separated into reusable headers
- Event-Driven: Structured events sent via PERF_EVENT_ARRAY maps
- Two-Stage Probes: Entry probes store context, return probes read results
- Stack Workarounds: Large structures allocated via PERCPU_ARRAY maps
- Dual Compilation: Supports both CO-RE (portable) and non-CO-RE (compatible) modes
- Filtering: Global variables enable PID/UID-based selective tracing
- Network Context: Kprobes build process-to-connection mappings for TC classifiers
All programs include comprehensive error handling with debug print statements and null checks for map lookups. The architecture maximizes code reuse through helper functions while maintaining clear separation between different capture modules.