Skip to content

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:

LayerResponsibilitiesKey Components
1. User InterfaceCommand parsing, configuration input, runtime updatesrootCmd (Cobra CLI), HTTP config server, eCaptureQ integration
2. Capture ModulesProtocol-specific logic, bytecode selection, probe attachmentIModule interface, MOpenSSLProbe, MGoTLSProbe, etc.
3. eBPF RuntimeVersion detection, CO-RE/non-CO-RE selection, eBPF program lifecyclemanager.Manager, uprobe/TC/kprobe programs, BTF detection
4. Event ProcessingEvent reading, aggregation, protocol parsing, connection trackingEventProcessor, eventWorker, IParser implementations
5. OutputFormat conversion, file writing, network streamingText/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

DecisionRationaleImplementation
Factory Pattern for ModulesEnables dynamic module loading based on CLI commandIModule interface user/module/imodule.go:47-75; modules register via RegisteFunc at package init
Dual Bytecode CompilationSupports both BTF-enabled (CO-RE) and non-BTF kernelsBuild system produces *_core.o and *_noncore.o variants; runtime selection via geteBPFName user/module/imodule.go:191-214
Version Detection LayerHandles 20+ OpenSSL/BoringSSL versions with different struct layoutsdetectOpenssl user/module/probe_openssl.go:178-278 parses ELF .rodata, maps version to bytecode via sslVersionBpfMap
Event Processing PipelineDecouples capture from output formatting, enables protocol parsingEventProcessor user/module/imodule.go:104 aggregates events by UUID, applies HTTP/HTTP2 parsers
Multiple Output FormatsSupports live analysis (text), forensics (PCAP), decryption (keylog)TlsCaptureModelType enum user/module/probe_openssl.go:58-76 controls capture mode
Connection TrackingMaps network packets to processes without userspace cooperationKprobes populate network_map LRU hash; TC hooks lookup PID/UID. See Network Connection Tracking
Dual Worker LifecycleOptimizes resource usage for different connection patternsSocket-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:

FlagTypeDefaultPurpose
--pid / -puint640 (all)Target specific process ID
--uid / -uuint640 (all)Target specific user ID
--btf / -buint80 (auto)BTF mode: 0=auto, 1=core, 2=non-core
--mapsizeint1024eBPF map size per CPU (KB)
--logaddr / -lstring""Log destination: file path, tcp://host:port, or ws://host:port/path
--eventaddrstring""Event destination (separate from logs)
--listenstringlocalhost:28256HTTP config server listen address
--tsize / -tuint640Truncate size in text mode (bytes, 0=no truncate)
--ecaptureqstring""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:

  1. Closes the current module (detaches eBPF programs)
  2. Creates a new module instance
  3. Initializes with updated configuration
  4. 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 --eventroratesize and --eventroratetime
  • TCP (type 2): Stream to tcp://host:port
  • WebSocket (type 3): Stream to ws://host:port/path or wss:// (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:

go
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

MethodPurposePhaseResponsibility
Init(context.Context, *zerolog.Logger, config.IConfig, io.Writer)Initialize module, setup EventProcessor, BTF detectionInitializationBase Module + child overrides
Start()Load eBPF bytecode, attach probes/hooksStartChild implements
Run()Start event readers, begin processing loopRunBase Module (calls child.Start)
Events() []*ebpf.MapReturn eBPF maps to read events fromRunChild implements
Decode(*ebpf.Map, []byte) (event.IEventStruct, error)Parse raw event bytes into structEvent ProcessingBase delegates to child.DecodeFun
DecodeFun(*ebpf.Map) (event.IEventStruct, bool)Return decoder for specific mapEvent ProcessingChild implements
Dispatcher(event.IEventStruct)Route events (cache, process, output)Event ProcessingBase + child both implement
Close()Stop eBPF programs, cleanup resourcesShutdownBase + 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:

  1. BTF Detection: autoDetectBTF() checks /sys/kernel/btf/vmlinux and container environment user/module/imodule.go:173-190
  2. Bytecode Selection: geteBPFName() appends _core.o/_noncore.o and _less52.o suffixes user/module/imodule.go:191-214
  3. Event Readers: perfEventReader() and ringbufEventReader() setup goroutines per eBPF map user/module/imodule.go:308-391
  4. EventProcessor: Initialized with truncate size and hex mode user/module/imodule.go:127
  5. Output Routing: Detects eventCollector type to select text vs protobuf encoding user/module/imodule.go:122-126, user/module/imodule.go:461-479
  6. 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

ModulePurposeTarget Libraries/BinariesKey StateSee Also
MOpenSSLProbeTLS plaintext capturelibssl.so, libcrypto.so, BoringSSLsslVersionBpfMap, pidConns, masterKeys, eBPFProgramTypeOpenSSL Module
MGoTLSProbeGo TLS plaintext captureGo binaries (crypto/tls)isRegisterABI, tcPacketsChan, keyloggerGo TLS Module
MGnuTLSProbeGnuTLS plaintext capturelibgnutls.sokeylogger, masterKeysGnuTLS and NSS Modules
MNSSProbeNSS/NSPR plaintext capturelibnss3.so, libnspr4.soMaster secret extractionGnuTLS and NSS Modules
MBashProbeBash command auditbash binaryCommand filtering via readline hooksShell Command Auditing
MZshProbeZsh command auditzsh binaryCommand filtering via zle hooksShell Command Auditing
MMysqldProbeMySQL query auditmysqld binaryfuncName, SQL extraction from dispatch_commandDatabase Query Auditing
MPostgresProbePostgreSQL query auditpostgres binaryQuery extraction from exec_simple_queryDatabase Query Auditing

Sources: user/module/probe_openssl.go:83-106

Example: MOpenSSLProbe State user/module/probe_openssl.go:83-106:

FieldTypePurpose
pidConnsmap[uint32]map[uint32]ConnInfoMaps PID → FD → connection tuple and socket user/module/probe_openssl.go:91
sock2pidFdmap[uint64][2]uint32Reverse map: socket → [PID, FD] for connection cleanup user/module/probe_openssl.go:93
masterKeysmap[string]boolDeduplicates TLS master secrets by client random user/module/probe_openssl.go:98
sslVersionBpfMapmap[string]stringMaps SSL version string to bytecode filename user/module/probe_openssl.go:101
eBPFProgramTypeTlsCaptureModelTypeDetermines capture mode (Text/Pcap/Keylog) user/module/probe_openssl.go:99
keylogger*os.FileFile handle for keylog mode output user/module/probe_openssl.go:96
bpfManager*manager.ManagereBPF 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:

  1. Version Detection: Determine target library version (see Version Detection and Bytecode Selection)
  2. Bytecode Selection: Choose CO-RE or non-CO-RE bytecode based on BTF availability
  3. Asset Loading: Load embedded bytecode from assets package
  4. eBPF Verification: Kernel verifies program safety
  5. Probe Attachment: Attach uprobes, TC classifiers, kprobes
  6. 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:

  1. Check if running in container (BTF detection may be unreliable in containers)
  2. Look for /sys/kernel/btf/vmlinux file to confirm BTF support
  3. Set m.isCoreUsed flag based on detection result

Filename Transformation Examples user/module/imodule.go:191-214:

  • openssl_3_0_0_kern.oopenssl_3_0_0_kern_core.o (BTF kernel >= 5.2)
  • openssl_3_0_0_kern.oopenssl_3_0_0_kern_noncore.o (non-BTF kernel >= 5.2)
  • openssl_3_0_0_kern.oopenssl_3_0_0_kern_core_less52.o (BTF kernel < 5.2)
  • openssl_3_0_0_kern.oopenssl_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:

  1. Setup: Start() calls mode-specific setup (setupManagersText, setupManagersPcap, setupManagersKeylog)
  2. Bytecode Load: assets.Asset(bpfFileName) retrieves embedded bytecode user/module/probe_openssl.go:312-317
  3. Initialization: bpfManager.InitWithOptions() loads bytecode, kernel verifies program user/module/probe_openssl.go:320-326
  4. Attachment: bpfManager.Start() attaches uprobes/TC/kprobes user/module/probe_openssl.go:329-331
  5. Map Registration: initDecodeFun*() populates eventMaps and eventFuncMaps user/module/probe_openssl.go:333-348
  6. Running: Base Module.Run() spawns event readers and EventProcessor user/module/imodule.go:236-262
  7. 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 NamePurposeTypeValue SourceEffect
target_pidFilter by process IDuint64conf.GetPid()0 = capture all PIDs, non-zero = specific PID only
target_uidFilter by user IDuint64conf.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 decryption
  • SSL_write / SSL_write_ex: Intercepts plaintext before encryption
  • SSL_do_handshake: Captures TLS handshake for master secret extraction
  • SSL_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:

  1. Capture encrypted packets at the network layer
  2. Look up which process owns the connection
  3. Filter packets based on target PID/UID
  4. 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 TypePurposeExamples
BPF_MAP_TYPE_PERF_EVENT_ARRAYStream events to userspaceevents, mastersecret_events, skb_events
BPF_MAP_TYPE_RINGBUFHigh-performance event streaming (kernel >= 5.8)Alternative to perf arrays
BPF_MAP_TYPE_LRU_HASHConnection tracking with automatic evictionnetwork_map, pidConns, sock2pidFd
BPF_MAP_TYPE_ARRAYConfiguration and constantstarget_pid, target_uid
BPF_MAP_TYPE_HASHGeneral key-value storageVarious 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:

  1. Map Iteration: Call child.Events() to get list of eBPF maps to read
  2. Reader Creation: Create appropriate reader (perf or ringbuf) based on map type
  3. Goroutine Per Map: Spawn goroutine for each map to read events concurrently
  4. Read Loop: Continuously call rd.Read() to fetch raw event bytes
  5. Decode: Call child.Decode(em, rawBytes) to parse into IEventStruct
  6. 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:

  1. Module's DecodeFun returns the appropriate event struct type for a given eBPF map
  2. Call es.Clone() to create a new instance of the event struct
  3. Call te.Decode(b) which uses encoding/binary to parse the raw bytes
  4. 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:

  1. TypeOutput: Events ready for direct output (e.g., parsed HTTP requests/responses)

  2. TypeEventProcessor: Events needing further processing (e.g., SSL data fragments)

    • Sent to EventProcessor for aggregation and protocol parsing
    • processor.Write(e) queues event for worker processing
  3. TypeModuleData: Events containing metadata (e.g., master secrets, connections)

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:

  1. Default Lifecycle: Auto-cleanup after 10 idle ticks (no events received)
  2. 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

Architecture has loaded