Skip to content

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.

DefinitionPurposeLine Reference
debug_bpf_printk()Conditional debug output macrokern/common.h:19-26
TASK_COMM_LENProcess name length (16 bytes)kern/common.h:28
MAX_DATA_SIZE_OPENSSLTLS data buffer size (16KB)kern/common.h:39
MAX_DATA_SIZE_MYSQLMySQL query buffer size (256B)kern/common.h:40
AF_INET, AF_INET6Address family constantskern/common.h:49-50
target_pid, target_uidGlobal filter variables (volatile)kern/common.h:68-69
less52Kernel version flag (< 5.2)kern/common.h:66

Key Features:

  1. Debug Printing: The debug_bpf_printk() macro conditionally compiles debug output based on DEBUG_PRINT flag. This avoids performance overhead in production builds.

  2. Buffer Size Constants: RFC-compliant sizes for different protocols (TLS max fragment = 16KB, database query limits).

  3. Global Variables: Marked volatile to 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.h for 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:

c
// 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 NameProbe TypePurposeExample Function
uprobe/<func>User-space entry probeHook function entrykern/openssl.h:331
uretprobe/<func>User-space return probeHook function returnkern/openssl.h:336
kprobe/<func>Kernel entry probeHook kernel function entrykern/openssl.h:374
kretprobe/<func>Kernel return probeHook kernel function returnkern/openssl.h:456
classifierTraffic Control classifierNetwork packet filteringkern/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?

  1. Function arguments are only valid at entry time
  2. Return values and buffer contents are only valid at return time
  3. 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:

c
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:

c
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:

c
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.

c
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:

HASH Maps for Context Storage

Store temporary data between probe entry and return:

c
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:

c
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:

c
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:

c
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:

c
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:

c
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:

c
#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:

c
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

SectionLine RangePurpose
License & Includeskern/openssl.h:1-16Header dependencies
Constants & Enumskern/openssl.h:19-26Event types, defaults
Event Structureskern/openssl.h:28-72Data format definitions
BPF Mapskern/openssl.h:74-134Storage declarations
Helper Functionskern/openssl.h:136-323Shared logic
Uprobe Entry Pointskern/openssl.h:325-351SSL_read/write hooks
Connection Trackingkern/openssl.h:354-525Kprobe implementations
SSL FD Mappingkern/openssl.h:528-543SSL_set_fd hook

Typical Program Flow

  1. Include Dependencies: Standard headers (ecapture.h, tc.h)
  2. Define Event Types: Enums and constants
  3. Declare Event Structures: Packed structs for user-space communication
  4. Declare BPF Maps: Storage for events, context, and large buffers
  5. Implement Helpers: Reusable logic for data extraction
  6. Implement Probes: Entry/return probe pairs with SEC() annotations
  7. 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

ModeFlagvmlinux.hKernel HeadersPortability
CO-RE(default)RequiredNot neededHigh - runs on any BTF kernel
Non-CO-RE-D NOCORENot usedRequiredLow - 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.h for all kernel types
  • Non-CO-RE: Manually includes kernel headers and may define simplified structs (e.g., struct tcphdr at 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:

c
#ifdef asm_inline
#undef asm_inline
#define asm_inline asm
#endif

At 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:

  1. Modular Headers: Common definitions separated into reusable headers
  2. Event-Driven: Structured events sent via PERF_EVENT_ARRAY maps
  3. Two-Stage Probes: Entry probes store context, return probes read results
  4. Stack Workarounds: Large structures allocated via PERCPU_ARRAY maps
  5. Dual Compilation: Supports both CO-RE (portable) and non-CO-RE (compatible) modes
  6. Filtering: Global variables enable PID/UID-based selective tracing
  7. 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.

eBPF Program Structure has loaded