Architecture
Relevant source files
The following files were used as context for generating this wiki page:
This document describes the overall system architecture of eCapture, explaining how different layers and components interact to capture and decrypt SSL/TLS traffic without requiring CA certificates. The architecture follows a clear separation of concerns across six major layers, from user interface through eBPF execution to formatted output.
For information about specific capture modules and their implementation details, see Capture Modules. For eBPF program development details, see eBPF Program Development. For build system details, see Build System.
Layered Architecture Overview
eCapture implements a layered architecture where each layer has distinct responsibilities. The system processes data from user commands through kernel-space eBPF programs to formatted output files or real-time streams.
Diagram: eCapture Layered Architecture
System Architecture Overview
eCapture implements a five-layer architecture with clear separation of concerns. Data flows from monitored applications through kernel-space eBPF hooks to userspace event processing, ultimately producing formatted output in multiple formats (text, PCAP-NG, keylog files, or protobuf streams).
Diagram: Five-Layer Architecture
The architecture makes several critical design decisions that enable its functionality:
Sources: cli/cmd/root.go:80-153, user/module/probe_openssl.go:83-106, user/module/imodule.go:47-75, user/module/probe_openssl.go:178-278, user/module/imodule.go:191-214
Architectural Layers Explained
Each layer has specific responsibilities:
| Layer | Responsibilities | Key Components |
|---|---|---|
| 1. User Interface | Command parsing, configuration input, runtime updates | rootCmd (Cobra CLI), HTTP config server, eCaptureQ integration |
| 2. Capture Modules | Protocol-specific logic, bytecode selection, probe attachment | IModule interface, MOpenSSLProbe, MGoTLSProbe, etc. |
| 3. eBPF Runtime | Version detection, CO-RE/non-CO-RE selection, eBPF program lifecycle | manager.Manager, uprobe/TC/kprobe programs, BTF detection |
| 4. Event Processing | Event reading, aggregation, protocol parsing, connection tracking | EventProcessor, eventWorker, IParser implementations |
| 5. Output | Format conversion, file writing, network streaming | Text/PCAP/Keylog/Protobuf writers, PCAP-NG DSB blocks |
See Module System and Lifecycle for details on the IModule interface and Event Processing Pipeline for event flow details.
Key Architectural Decisions
| Decision | Rationale | Implementation |
|---|---|---|
| Factory Pattern for Modules | Enables dynamic module loading based on CLI command | IModule interface user/module/imodule.go:47-75; modules register via RegisteFunc at package init |
| Dual Bytecode Compilation | Supports both BTF-enabled (CO-RE) and non-BTF kernels | Build system produces *_core.o and *_noncore.o variants; runtime selection via geteBPFName user/module/imodule.go:191-214 |
| Version Detection Layer | Handles 20+ OpenSSL/BoringSSL versions with different struct layouts | detectOpenssl user/module/probe_openssl.go:178-278 parses ELF .rodata, maps version to bytecode via sslVersionBpfMap |
| Event Processing Pipeline | Decouples capture from output formatting, enables protocol parsing | EventProcessor user/module/imodule.go:104 aggregates events by UUID, applies HTTP/HTTP2 parsers |
| Multiple Output Formats | Supports live analysis (text), forensics (PCAP), decryption (keylog) | TlsCaptureModelType enum user/module/probe_openssl.go:58-76 controls capture mode |
| Connection Tracking | Maps network packets to processes without userspace cooperation | Kprobes populate network_map LRU hash; TC hooks lookup PID/UID. See Network Connection Tracking |
| Dual Worker Lifecycle | Optimizes resource usage for different connection patterns | Socket-based lifecycle for persistent connections, default (10-tick timeout) for short-lived. See Event Processing Pipeline |
Sources: user/module/imodule.go:47-75, user/module/probe_openssl.go:58-76, user/module/probe_openssl.go:178-278, user/module/imodule.go:191-214
Data Flow Pipeline
The following diagram shows how data flows through the system from application to output:
Diagram: Complete Data Flow
Sources: user/module/imodule.go:285-391, user/module/imodule.go:409-448, cli/cmd/root.go:250-403
User Interface Layer
eCapture provides three interfaces for user interaction: CLI commands, HTTP configuration API, and eCaptureQ GUI integration.
CLI Entry Point
The CLI uses Cobra command framework. Each subcommand corresponds to a capture module.
Diagram: CLI Command Structure
Sources: main.go:9-11, cli/cmd/root.go:80-153, cli/cmd/root.go:250-403, cli/cmd/root.go:156-175
Persistent Flags (apply to all modules) cli/cmd/root.go:140-153:
| Flag | Type | Default | Purpose |
|---|---|---|---|
--pid / -p | uint64 | 0 (all) | Target specific process ID |
--uid / -u | uint64 | 0 (all) | Target specific user ID |
--btf / -b | uint8 | 0 (auto) | BTF mode: 0=auto, 1=core, 2=non-core |
--mapsize | int | 1024 | eBPF map size per CPU (KB) |
--logaddr / -l | string | "" | Log destination: file path, tcp://host:port, or ws://host:port/path |
--eventaddr | string | "" | Event destination (separate from logs) |
--listen | string | localhost:28256 | HTTP config server listen address |
--tsize / -t | uint64 | 0 | Truncate size in text mode (bytes, 0=no truncate) |
--ecaptureq | string | "" | Listen for eCaptureQ client connections |
HTTP Configuration Server
An HTTP server runs concurrently to accept runtime configuration updates without restarting.
Diagram: Runtime Configuration Update
Sources: cli/cmd/root.go:313-322, cli/cmd/root.go:368-396
The HTTP server enables dynamic reconfiguration. When a POST request with updated configuration JSON arrives, the system:
- Closes the current module (detaches eBPF programs)
- Creates a new module instance
- Initializes with updated configuration
- Restarts event capture with new settings
See Configuration System for configuration structure details and HTTP API Documentation for API details.
Output Destinations
eCapture supports multiple output destinations for logs and events:
Diagram: Output Routing
Sources: cli/cmd/root.go:178-247, cli/cmd/root.go:255-295
Output types cli/cmd/root.go:69-73:
- Stdout (type 0): Console output only
- File (type 1): Write to local file, optionally with rotation via
--eventroratesizeand--eventroratetime - TCP (type 2): Stream to
tcp://host:port - WebSocket (type 3): Stream to
ws://host:port/pathorwss://(TLS)
The eventCollector receives captured events while the logger receives operational logs. They can use the same or different destinations via --logaddr and --eventaddr flags.
Capture Module Layer
The module system uses a factory pattern for dynamic module instantiation. Each module implements the IModule interface and embeds the base Module struct for common functionality.
Module Factory and Registration
Modules self-register at package initialization time.
Diagram: Module Factory Pattern
Sources: user/module/probe_openssl.go:777-786, cli/cmd/root.go:344-347
Example registration from OpenSSL module user/module/probe_openssl.go:777-786:
func init() {
RegisteFunc(NewOpenSSLProbe)
}
func NewOpenSSLProbe() IModule {
mod := &MOpenSSLProbe{}
mod.name = ModuleNameOpenssl
mod.mType = ProbeTypeUprobe
return mod
}The CLI retrieves the constructor via module.GetModuleFunc(modName) cli/cmd/root.go:344 and invokes it to create an instance.
IModule Interface
All modules implement the IModule interface user/module/imodule.go:47-75, which defines lifecycle and event processing methods.
IModule Interface Methods
| Method | Purpose | Phase | Responsibility |
|---|---|---|---|
Init(context.Context, *zerolog.Logger, config.IConfig, io.Writer) | Initialize module, setup EventProcessor, BTF detection | Initialization | Base Module + child overrides |
Start() | Load eBPF bytecode, attach probes/hooks | Start | Child implements |
Run() | Start event readers, begin processing loop | Run | Base Module (calls child.Start) |
Events() []*ebpf.Map | Return eBPF maps to read events from | Run | Child implements |
Decode(*ebpf.Map, []byte) (event.IEventStruct, error) | Parse raw event bytes into struct | Event Processing | Base delegates to child.DecodeFun |
DecodeFun(*ebpf.Map) (event.IEventStruct, bool) | Return decoder for specific map | Event Processing | Child implements |
Dispatcher(event.IEventStruct) | Route events (cache, process, output) | Event Processing | Base + child both implement |
Close() | Stop eBPF programs, cleanup resources | Shutdown | Base + child both implement |
See Module System and Lifecycle for detailed lifecycle information.
Sources: user/module/imodule.go:47-75, user/module/imodule.go:110-171
Base Module Implementation
The Module struct user/module/imodule.go:83-108 provides common functionality that all probes inherit through embedding.
Diagram: Module Struct Composition
Sources: user/module/imodule.go:83-108, user/module/probe_openssl.go:83-106
Base Module Responsibilities user/module/imodule.go:83-460:
- BTF Detection:
autoDetectBTF()checks/sys/kernel/btf/vmlinuxand container environment user/module/imodule.go:173-190 - Bytecode Selection:
geteBPFName()appends_core.o/_noncore.oand_less52.osuffixes user/module/imodule.go:191-214 - Event Readers:
perfEventReader()andringbufEventReader()setup goroutines per eBPF map user/module/imodule.go:308-391 - EventProcessor: Initialized with truncate size and hex mode user/module/imodule.go:127
- Output Routing: Detects
eventCollectortype to select text vs protobuf encoding user/module/imodule.go:122-126, user/module/imodule.go:461-479 - Lifecycle Management: Coordinates child module's lifecycle through
Start(),Run(),Close()user/module/imodule.go:236-262
Module-Specific Implementations
Each probe module embeds Module and adds module-specific state and logic. See Capture Modules for detailed implementation information.
Key Module Types
| Module | Purpose | Target Libraries/Binaries | Key State | See Also |
|---|---|---|---|---|
MOpenSSLProbe | TLS plaintext capture | libssl.so, libcrypto.so, BoringSSL | sslVersionBpfMap, pidConns, masterKeys, eBPFProgramType | OpenSSL Module |
MGoTLSProbe | Go TLS plaintext capture | Go binaries (crypto/tls) | isRegisterABI, tcPacketsChan, keylogger | Go TLS Module |
MGnuTLSProbe | GnuTLS plaintext capture | libgnutls.so | keylogger, masterKeys | GnuTLS and NSS Modules |
MNSSProbe | NSS/NSPR plaintext capture | libnss3.so, libnspr4.so | Master secret extraction | GnuTLS and NSS Modules |
MBashProbe | Bash command audit | bash binary | Command filtering via readline hooks | Shell Command Auditing |
MZshProbe | Zsh command audit | zsh binary | Command filtering via zle hooks | Shell Command Auditing |
MMysqldProbe | MySQL query audit | mysqld binary | funcName, SQL extraction from dispatch_command | Database Query Auditing |
MPostgresProbe | PostgreSQL query audit | postgres binary | Query extraction from exec_simple_query | Database Query Auditing |
Sources: user/module/probe_openssl.go:83-106
Example: MOpenSSLProbe State user/module/probe_openssl.go:83-106:
| Field | Type | Purpose |
|---|---|---|
pidConns | map[uint32]map[uint32]ConnInfo | Maps PID → FD → connection tuple and socket user/module/probe_openssl.go:91 |
sock2pidFd | map[uint64][2]uint32 | Reverse map: socket → [PID, FD] for connection cleanup user/module/probe_openssl.go:93 |
masterKeys | map[string]bool | Deduplicates TLS master secrets by client random user/module/probe_openssl.go:98 |
sslVersionBpfMap | map[string]string | Maps SSL version string to bytecode filename user/module/probe_openssl.go:101 |
eBPFProgramType | TlsCaptureModelType | Determines capture mode (Text/Pcap/Keylog) user/module/probe_openssl.go:99 |
keylogger | *os.File | File handle for keylog mode output user/module/probe_openssl.go:96 |
bpfManager | *manager.Manager | eBPF program lifecycle manager user/module/probe_openssl.go:85 |
These maps enable correlation between SSL data events (identified by PID/FD) and network tuples captured by TC hooks. See Version Detection and Bytecode Selection for sslVersionBpfMap usage and Network Connection Tracking for connection mapping details.
eBPF Runtime Layer
The eBPF runtime layer bridges userspace modules with kernel-space instrumentation. It handles version detection, bytecode selection, and eBPF program lifecycle through the ebpfmanager library.
For comprehensive details on eBPF programs and hooks, see eBPF Engine. For version detection algorithms, see Version Detection and Bytecode Selection.
Overview of eBPF Runtime Components
Diagram: eBPF Runtime Components
Sources: user/module/probe_openssl.go:178-278, user/module/imodule.go:191-214, user/module/probe_openssl.go:312-331, user/module/imodule.go:285-391
The runtime layer performs these operations:
- Version Detection: Determine target library version (see Version Detection and Bytecode Selection)
- Bytecode Selection: Choose CO-RE or non-CO-RE bytecode based on BTF availability
- Asset Loading: Load embedded bytecode from
assetspackage - eBPF Verification: Kernel verifies program safety
- Probe Attachment: Attach uprobes, TC classifiers, kprobes
- Event Reading: Setup readers for eBPF maps
BTF Detection and Bytecode Selection
eCapture compiles two variants of each eBPF program: CO-RE (BTF-enabled, kernel >= 5.2) and non-CO-RE (traditional, all kernels). Runtime selection is based on kernel BTF support.
Diagram: BTF Detection and Bytecode Mode Selection
Sources: user/module/imodule.go:154-170, user/module/imodule.go:173-190, user/module/imodule.go:191-214
BTF Detection Logic user/module/imodule.go:173-190:
- Check if running in container (BTF detection may be unreliable in containers)
- Look for
/sys/kernel/btf/vmlinuxfile to confirm BTF support - Set
m.isCoreUsedflag based on detection result
Filename Transformation Examples user/module/imodule.go:191-214:
openssl_3_0_0_kern.o→openssl_3_0_0_kern_core.o(BTF kernel >= 5.2)openssl_3_0_0_kern.o→openssl_3_0_0_kern_noncore.o(non-BTF kernel >= 5.2)openssl_3_0_0_kern.o→openssl_3_0_0_kern_core_less52.o(BTF kernel < 5.2)openssl_3_0_0_kern.o→openssl_3_0_0_kern_noncore_less52.o(non-BTF kernel < 5.2)
CO-RE bytecode uses BTF type information for structure layout resolution at load time, enabling Compile Once - Run Everywhere. Non-CO-RE bytecode has hardcoded offsets for specific kernel versions. See Build System for compilation details.
eBPF Program Lifecycle
The ebpfmanager.Manager user/module/probe_openssl.go:85 manages eBPF program loading, verification, attachment, and cleanup.
Sources: user/module/probe_openssl.go:280-357, user/module/imodule.go:236-262
Lifecycle Phases:
- Setup:
Start()calls mode-specific setup (setupManagersText,setupManagersPcap,setupManagersKeylog) - Bytecode Load:
assets.Asset(bpfFileName)retrieves embedded bytecode user/module/probe_openssl.go:312-317 - Initialization:
bpfManager.InitWithOptions()loads bytecode, kernel verifies program user/module/probe_openssl.go:320-326 - Attachment:
bpfManager.Start()attaches uprobes/TC/kprobes user/module/probe_openssl.go:329-331 - Map Registration:
initDecodeFun*()populateseventMapsandeventFuncMapsuser/module/probe_openssl.go:333-348 - Running: Base
Module.Run()spawns event readers and EventProcessor user/module/imodule.go:236-262 - Shutdown:
bpfManager.Stop(manager.CleanAll)detaches and cleans up user/module/probe_openssl.go:352-357
Configuration Injection via Constant Editors
eBPF programs define constant variables that are rewritten at load time to inject runtime configuration (PID, UID filters).
Constant Editor Mechanism
| Constant Name | Purpose | Type | Value Source | Effect |
|---|---|---|---|---|
target_pid | Filter by process ID | uint64 | conf.GetPid() | 0 = capture all PIDs, non-zero = specific PID only |
target_uid | Filter by user ID | uint64 | conf.GetUid() | 0 = capture all UIDs, non-zero = specific UID only |
Sources: user/module/probe_openssl.go:361-387
The constantEditor() method user/module/probe_openssl.go:361-387 returns a slice of manager.ConstantEditor structs. The eBPF manager rewrites these constants in the bytecode before loading into the kernel. This enables parameterized filtering without recompiling eBPF programs.
For kernels < 5.2, global variable support is limited. The EnableGlobalVar() check user/config/iconfig.go:194-203 returns false, disabling certain features.
Uprobe Attachments
Uprobes instrument user-space library functions to capture plaintext data before/after encryption.
Diagram: Uprobe Hook Points
Sources: user/module/probe_openssl.go:85-96
Each uprobe captures function arguments and return values from target library functions. For OpenSSL, key hooks include:
SSL_read/SSL_read_ex: Intercepts plaintext after decryptionSSL_write/SSL_write_ex: Intercepts plaintext before encryptionSSL_do_handshake: Captures TLS handshake for master secret extractionSSL_get_wbio: Extracts BIO structure to get socket file descriptor
TC (Traffic Control) Hooks
TC classifier programs attach to network interfaces to capture encrypted packets with network metadata.
Diagram: TC Hook Architecture
Sources: TC hooks capture complete packets including:
- Ethernet, IP, and TCP/UDP headers
- Encrypted TLS payload
- 5-tuple (source IP:port, dest IP:port, protocol)
- Timestamp and packet length
The TC programs lookup the network tuple in network_map to determine which process owns the connection, enabling process-level filtering even for encrypted traffic.
Kprobe Attachments
Kprobes hook kernel functions to build network context mappings that correlate packets with processes.
Diagram: Kprobe Context Tracking
Sources: Kprobes populate the network_map LRU hash map with entries mapping network 5-tuples to process identifiers. This enables TC hooks to:
- Capture encrypted packets at the network layer
- Look up which process owns the connection
- Filter packets based on target PID/UID
- Associate captured packets with the correct capture session
eBPF Map Types
Different map types serve different purposes in the data pipeline.
Map Type Summary
| Map Type | Purpose | Examples |
|---|---|---|
BPF_MAP_TYPE_PERF_EVENT_ARRAY | Stream events to userspace | events, mastersecret_events, skb_events |
BPF_MAP_TYPE_RINGBUF | High-performance event streaming (kernel >= 5.8) | Alternative to perf arrays |
BPF_MAP_TYPE_LRU_HASH | Connection tracking with automatic eviction | network_map, pidConns, sock2pidFd |
BPF_MAP_TYPE_ARRAY | Configuration and constants | target_pid, target_uid |
BPF_MAP_TYPE_HASH | General key-value storage | Various module-specific maps |
Sources: user/module/imodule.go:294-306
Event Processing Pipeline
Once events are captured in kernel space, they flow through userspace processing to produce formatted output.
Event Reading from eBPF Maps
The base Module struct sets up readers for each eBPF map based on its type.
Diagram: Event Reader Setup
Sources: user/module/imodule.go:285-391
The event reading process user/module/imodule.go:285-391:
- Map Iteration: Call
child.Events()to get list of eBPF maps to read - Reader Creation: Create appropriate reader (perf or ringbuf) based on map type
- Goroutine Per Map: Spawn goroutine for each map to read events concurrently
- Read Loop: Continuously call
rd.Read()to fetch raw event bytes - Decode: Call
child.Decode(em, rawBytes)to parse intoIEventStruct - Dispatch: Route event via
Module.Dispatcher(evt)
The perf event reader user/module/imodule.go:308-351 creates a buffer of configurable size (via --mapsize flag) per CPU core. Lost samples are logged when the buffer fills.
Event Decoding
Each module implements its own Decode method that parses raw bytes into structured events.
Diagram: Decode Function Dispatch
Sources: user/module/imodule.go:393-406, user/module/probe_openssl.go:389-392
The decode process:
- Module's
DecodeFunreturns the appropriate event struct type for a given eBPF map - Call
es.Clone()to create a new instance of the event struct - Call
te.Decode(b)which usesencoding/binaryto parse the raw bytes - Return the populated
IEventStruct
Each event type implements the IEventStruct interface which defines Decode([]byte) error, Clone() IEventStruct, EventType(), and output methods.
Event Dispatcher and Routing
The Dispatcher method routes decoded events to appropriate handlers based on event type.
Diagram: Event Routing Logic
Sources: user/module/imodule.go:409-448
The dispatcher user/module/imodule.go:409-448 implements three routing paths:
TypeOutput: Events ready for direct output (e.g., parsed HTTP requests/responses)
- Encoded as text or protobuf based on
eventOutputTypeuser/module/imodule.go:461-479 - Written directly to
eventCollector(logger or WebSocket)
- Encoded as text or protobuf based on
TypeEventProcessor: Events needing further processing (e.g., SSL data fragments)
- Sent to
EventProcessorfor aggregation and protocol parsing processor.Write(e)queues event for worker processing
- Sent to
TypeModuleData: Events containing metadata (e.g., master secrets, connections)
- Routed to child module's
Dispatcherfor module-specific handling - OpenSSL module saves master secrets user/module/probe_openssl.go:733-754
- Connection info cached for tuple resolution user/module/probe_openssl.go:398-416
- Routed to child module's
EventProcessor and Worker Lifecycle
The EventProcessor aggregates fragmented events by connection and applies protocol-aware parsing.
Diagram: EventProcessor Architecture
Sources: The EventProcessor manages a pool of eventWorker goroutines, each responsible for a specific connection UUID. Workers implement two lifecycle models:
- Default Lifecycle: Auto-cleanup after 10 idle ticks (no events received)
- Socket Lifecycle: Persists until explicit cleanup via socket destruction
Each worker maintains a queue and processes events sequentially to preserve ordering. Protocol parsers detect HTTP/1.1, HTTP/2, and other protocols, applying format-specific decoding (e.g., HPACK decompression for HTTP/2).
Configuration and Capture Modes
eCapture supports multiple capture modes that determine how data is processed and output.
Sources: user/module/probe_openssl.go:58-76, user/module/probe_openssl.go:127-154
Module Lifecycle
Each module follows a standardized lifecycle managed by the base Module struct and implemented by specific probes.
Sources: user/module/imodule.go:99-152, user/module/imodule.go:218-244, user/module/probe_openssl.go:285-355, user/module/imodule.go:430-440