Architecture
Relevant source files
The following files were used as context for generating this wiki page:
This document describes the overall architecture of eCapture, explaining how the system is structured into layers and how data flows from the command-line interface through eBPF probes to final output. The architecture follows a five-layer design: CLI Layer → Module Orchestration → eBPF Execution → Event Processing → Output.
For details on specific capture modules (OpenSSL, GoTLS, etc.), see Capture Modules. For information about the eBPF implementation, see eBPF Engine. For event processing internals, see Event Processing Pipeline.
System Overview
eCapture is organized as a modular eBPF-based capture system. The architecture separates concerns into distinct layers, allowing new capture modules to be added without modifying core infrastructure. Each module implements the IModule interface and manages its own eBPF programs, while sharing common event processing and output mechanisms.
Sources: README.md:36-44, cli/cmd/root.go:44-51, user/module/imodule.go:47-75
Five-Layer Architecture
Architecture Overview: Five distinct layers with clear separation of concerns
The architecture consists of five primary layers:
- CLI Layer: Parses commands and flags, manages configuration
- Module Orchestration Layer: Implements the
IModuleinterface pattern, coordinates module lifecycle - eBPF Execution Layer: Loads and manages eBPF programs, attaches probes to target functions
- Event Processing Layer: Aggregates and parses raw eBPF events into structured data
- Output Layer: Formats and writes processed events to various destinations
Sources: cli/cmd/root.go:80-133, user/module/imodule.go:47-75, user/module/probe_openssl.go:83-106
CLI Layer
The CLI layer is implemented using the Cobra framework and provides the entry point for all eCapture operations.
CLI Command Flow: From user input to module execution
The rootCmd in cli/cmd/root.go:81-113 is the root Cobra command. It defines global flags that apply to all subcommands:
| Flag | Type | Purpose | Default |
|---|---|---|---|
--pid / -p | uint64 | Target process ID (0 = all processes) | 0 |
--uid / -u | uint64 | Target user ID (0 = all users) | 0 |
--debug / -d | bool | Enable debug logging | false |
--btf / -b | uint8 | BTF mode (0=auto, 1=core, 2=non-core) | 0 |
--mapsize | int | eBPF map size per CPU (KB) | 1024 |
--logaddr / -l | string | Logger output address | "" |
--listen | string | HTTP API listen address | "localhost:28256" |
Each subcommand (e.g., tls, gotls, bash) eventually calls runModule() at cli/cmd/root.go:250-403, which:
- Creates module-specific configuration from global configuration using
setModConfig()cli/cmd/root.go:157-175 - Initializes loggers and event collectors cli/cmd/root.go:282-295
- Starts an HTTP server for runtime configuration updates cli/cmd/root.go:313-322
- Initializes the module via
IModule.Init()cli/cmd/root.go:352-356 - Runs the module via
IModule.Run()cli/cmd/root.go:358-362 - Handles signals for reload or shutdown cli/cmd/root.go:367-396
Sources: cli/cmd/root.go:80-154, cli/cmd/root.go:157-175, cli/cmd/root.go:250-403
Module Orchestration Layer
The module orchestration layer is centered around the IModule interface, which all capture modules implement.
IModule Interface and Implementations
The IModule interface at user/module/imodule.go:47-75 defines the contract for all capture modules:
Init(context.Context, *zerolog.Logger, config.IConfig, io.Writer) error: Initialize the module with context, logger, configuration, and event writerName() string: Return the module nameStart() error: Start the eBPF programs and attach probesRun() error: Begin reading events from eBPF mapsEvents() []*ebpf.Map: Return the eBPF maps that contain eventsDecodeFun(*ebpf.Map) (event.IEventStruct, bool): Return the decoder function for a specific mapDispatcher(event.IEventStruct): Process and route decoded eventsClose() error: Clean up resources
The Module base class at user/module/imodule.go:83-108 provides common functionality:
- Event reading from perf buffers and ring buffers user/module/imodule.go:285-391
- Event decoding user/module/imodule.go:393-406
- Event dispatching to the event processor user/module/imodule.go:408-448
- BTF (BPF Type Format) detection user/module/imodule.go:173-190
- Bytecode file selection (CO-RE vs non-CO-RE) user/module/imodule.go:191-214
Sources: user/module/imodule.go:47-108, user/module/imodule.go:236-262, user/module/imodule.go:285-391
Module Lifecycle
The module lifecycle follows a three-phase pattern: Init → Run → Close.
Module Lifecycle: Three-phase initialization, execution, and cleanup
Init Phase
The Init() method performs module initialization:
- Context and logger setup at user/module/imodule.go:111-127
- BTF detection using
autoDetectBTF()at user/module/imodule.go:173-190 - Kernel version check at user/module/imodule.go:140-149
- EventProcessor creation at user/module/imodule.go:127
- Child-specific initialization (e.g., OpenSSL version detection at user/module/probe_openssl.go:109-176)
Run Phase
The Run() method orchestrates execution:
- Call
Start()on the child module at user/module/imodule.go:239-242 - Start event reading goroutines at user/module/imodule.go:256-259
- Start EventProcessor at user/module/imodule.go:249-254
- Read events from eBPF maps at user/module/imodule.go:285-305
The Start() method (implemented by child modules) loads and attaches eBPF programs:
- Setup managers based on capture mode at user/module/probe_openssl.go:284-300
- Load bytecode from embedded assets at user/module/probe_openssl.go:310-326
- Initialize bpfManager at user/module/probe_openssl.go:320-326
- Start bpfManager (attach probes) at user/module/probe_openssl.go:328-331
- Initialize decode functions at user/module/probe_openssl.go:333-347
Close Phase
The Close() method performs cleanup:
- Stop bpfManager and detach probes at user/module/probe_openssl.go:352-357
- Close EventProcessor at user/module/imodule.go:458-459
- Close event readers at user/module/imodule.go:453-457
Sources: user/module/imodule.go:111-171, user/module/imodule.go:236-262, user/module/probe_openssl.go:109-176, user/module/probe_openssl.go:280-350
eBPF Execution Layer
The eBPF execution layer manages the loading, initialization, and lifecycle of eBPF programs.
eBPF Program Loading and Attachment
Bytecode Selection
eCapture uses different eBPF bytecode files depending on:
- Target library version: OpenSSL 1.0.x, 1.1.x, 3.0.x, 3.x, BoringSSL variants user/module/probe_openssl.go:178-278
- CO-RE support: Kernel BTF availability determines CO-RE vs non-CO-RE bytecode user/module/imodule.go:173-190
- Kernel version: Kernels < 5.2 have different limitations user/module/imodule.go:140-149
The geteBPFName() method at user/module/imodule.go:191-214 selects the appropriate bytecode file by appending _core.o or _noncore.o to the base filename.
Manager Initialization
The bpfManager from the ebpfmanager library manages eBPF program lifecycle:
- Load bytecode from embedded assets via
assets.Asset()user/module/probe_openssl.go:312-317 - Initialize manager with
InitWithOptions()user/module/probe_openssl.go:320-326 - Start manager to attach probes with
Start()user/module/probe_openssl.go:328-331
The bpfManagerOptions struct contains:
- Constants: Target PID, UID, kernel version flags user/module/probe_openssl.go:361-395
- Probes: List of uprobe/kprobe/TC programs to attach
- Maps: References to eBPF maps for event reading
Event Maps
Each module defines eBPF maps for event collection:
- PerfEventArray or RingBuf maps for event streaming
- Managed by the eBPF manager and accessed via the
Events()method user/module/imodule.go:224-226 - Event reading handled by
perfEventReader()orringbufEventReader()user/module/imodule.go:308-391
Sources: user/module/probe_openssl.go:178-278, user/module/probe_openssl.go:280-350, user/module/imodule.go:173-214, user/module/imodule.go:308-391
Event Processing Layer
The event processing layer aggregates raw eBPF events, buffers payloads, and parses protocol data. For detailed information, see Event Processing Pipeline.
Event Processing: Aggregation, buffering, and parsing
Event Decoding
Raw bytes from eBPF maps are decoded into event structures:
- Get decoder function via
DecodeFun()user/module/imodule.go:228-230 - Decode bytes into event struct via
Decode()user/module/imodule.go:393-406 - Dispatch event via
Dispatcher()user/module/imodule.go:408-448
Event Processor
The EventProcessor at user/module/imodule.go:127 manages worker pools:
- UUID-based routing: Events with the same UUID (connection ID) go to the same worker
- Worker lifecycle: Workers are created on-demand and destroyed after inactivity
- Buffered accumulation: Workers accumulate event fragments before parsing
See Event Processing Pipeline for implementation details.
Sources: user/module/imodule.go:285-448, user/module/probe_openssl.go:741-783
Output Layer
The output layer formats processed events and writes them to configured destinations.
Output Formatting and Destinations
Output Format Selection
The output format is determined by the eventCollector writer type:
- Text mode: When
eventCollectorisCollectorWriteruser/module/imodule.go:122-126 - Protobuf mode: When
eventCollectorisecaptureQEventWriteruser/module/imodule.go:122-126
The format is applied in Module.output() at user/module/imodule.go:461-479:
if m.eventOutputType == codecTypeProtobuf {
// Marshal to protobuf
le := new(pb.LogEntry)
le.LogType = pb.LogType_LOG_TYPE_EVENT
ep := e.ToProtobufEvent()
...
} else {
// Convert to string
s := e.String()
...
}Output Destinations
Output destinations are configured via the --logaddr and --eventaddr flags:
| Destination Type | Flag Format | Implementation |
|---|---|---|
| Stdout (default) | (none) | zerolog.ConsoleWriter to os.Stdout |
| File | /path/to/file.log | os.Create() file handle |
| TCP | tcp://host:port | net.Dial("tcp", addr) |
| WebSocket | ws://host:port/path | ws.NewClient().Dial() |
Logger initialization at cli/cmd/root.go:178-247 creates appropriate writers based on the address format.
Module-Specific Output
Some modules have specialized output modes:
- PCAP mode: Writes pcapng format with DSB (Decryption Secrets Block) for Wireshark user/config/iconfig.go:73-79
- Keylog mode: Writes TLS master secrets in SSLKEYLOGFILE format user/config/iconfig.go:73-79
- Text mode: Direct plaintext output with protocol parsing user/config/iconfig.go:73-79
See Output Formats for details on each format.
Sources: user/module/imodule.go:111-127, user/module/imodule.go:461-479, cli/cmd/root.go:178-247, user/config/iconfig.go:73-79
Data Flow Summary
The complete data flow through the architecture:
- User executes CLI command →
rootCmd.Execute()parses flags - Subcommand handler calls
runModule()with module name and config - Module initialization →
IModule.Init()detects libraries, selects bytecode - Module start →
IModule.Run()loads eBPF, attaches probes, starts event processor - eBPF probes capture data in kernel, write to maps
- Event readers poll maps, decode bytes into event structs
- Dispatcher routes events to event processor or module-specific handlers
- Event processor aggregates fragments, buffers payloads, parses protocols
- Output formatters convert to text or protobuf
- Writers send to stdout, file, TCP, or WebSocket
This architecture provides:
- Modularity: New modules implement
IModulewithout changing core code - Flexibility: Multiple output formats and destinations
- Performance: Asynchronous event processing with worker pools
- Extensibility: Protocol parsers and output writers are pluggable
Sources: cli/cmd/root.go:250-403, user/module/imodule.go:236-262, user/module/imodule.go:285-448