Skip to content

Module System and Lifecycle

Relevant source files

The following files were used as context for generating this wiki page:

This document explains the module system architecture in eCapture, including the IModule interface contract, module registration patterns, and the complete lifecycle from initialization through shutdown. For details on how modules configure themselves, see Configuration System. For information on eBPF program loading within modules, see eBPF Engine.

IModule Interface Contract

The IModule interface defines the standard contract that all capture modules must implement. This interface enables polymorphic module handling throughout the system, allowing the CLI and HTTP API to instantiate and manage different capture types (TLS, Bash, MySQL, etc.) uniformly.

Core Interface Definition

MethodPurposeCalled By
Init(context.Context, *zerolog.Logger, config.IConfig, io.Writer)Initialize module with context, logger, config, and event writerCLI/HTTP handler before starting
Name()Return module name (e.g., "openssl", "gotls")Logging and identification
Run()Start event processing pipelineCLI after initialization completes
Start()Load and attach eBPF programsModule.Run() internally
Stop()Gracefully stop captureSignal handlers
Close()Release all resourcesShutdown sequence
SetChild(IModule)Register concrete implementation for parent-child patternInit() phase
Events()Return list of eBPF maps for event readingEvent reader setup
DecodeFun(*ebpf.Map)Get decoder for specific eBPF mapEvent decoding
Decode(*ebpf.Map, []byte)Decode raw bytes to event structEvent processing
Dispatcher(event.IEventStruct)Handle decoded eventsEvent routing

Sources: user/module/imodule.go:47-75

Diagram: IModule Interface Hierarchy

The parent-child pattern allows the base Module struct to provide common functionality (BTF detection, event reading, context management) while delegating module-specific operations (eBPF program setup, event decoding) to concrete implementations via the child field.

Sources: user/module/imodule.go:83-108, user/module/probe_openssl.go:83-106

Module Registration and Factory Pattern

eCapture uses a factory pattern for module instantiation, enabling dynamic module loading based on CLI commands. Each module registers a factory function during package initialization.

Registration Mechanism

Diagram: Module Registration and Factory Flow

Sources: user/module/probe_openssl.go:777-786

Module Name Constants

Each module is identified by a unique string constant used for registry lookups:

Module NameConstantTarget
ModuleNameOpenssl"openssl"OpenSSL/BoringSSL
ModuleNameGotls"gotls"Go crypto/tls
ModuleNameGnutls"gnutls"GnuTLS
ModuleNameNspr"nspr"NSS/NSPR
ModuleNameBash"bash"Bash shell
ModuleNameZsh"zsh"Zsh shell
ModuleNameMysqld"mysqld"MySQL server
ModuleNamePostgres"postgres"PostgreSQL

CLI-to-Module Mapping

The CLI layer maps cobra commands to modules via the runModule() function:

Sources: cli/cmd/tls.go:62-67, cli/cmd/gotls.go:52-58, cli/cmd/bash.go:53-55

Module Lifecycle Stages

The module lifecycle consists of five distinct phases, each with specific responsibilities. Modules progress through these stages sequentially, with error handling at each transition point.

Diagram: Module Lifecycle State Machine

Sources: cli/cmd/root.go:336-397, user/module/imodule.go:110-171

Initialization Phase (Init)

The Init() method prepares the module for execution. This is where the module receives its dependencies and performs environment detection.

Initialization Responsibilities:

  1. Context Inheritance: Store parent context for cancellation propagation
  2. Logger Configuration: Set up zerolog instance for module-specific logging
  3. Config Storage: Save IConfig reference for runtime parameter access
  4. BTF Mode Detection: Determine whether to use CO-RE or non-CO-RE bytecode
  5. Kernel Version Check: Detect if kernel version < 5.2 (affects PID filtering)
  6. EventProcessor Creation: Initialize event aggregation and formatting pipeline
  7. Output Format Selection: Choose between text/protobuf based on event collector type

Base Module Init Implementation:

user/module/imodule.go:111-171 shows the common initialization logic:

m.isClosed.Store(false)
m.ctx = ctx
m.logger = logger
m.errChan = make(chan error, 16)

// BTF auto-detection
if conf.GetBTF() == config.BTFModeAutoDetect {
    m.autoDetectBTF()
} else {
    m.isCoreUsed = (conf.GetBTF() == config.BTFModeCore)
}

// Kernel version detection
kv, err := kernel.HostVersion()
if kv < kernel.VersionCode(5, 2, 0) {
    m.isKernelLess5_2 = true
}

// EventProcessor with truncation support
m.processor = event_processor.NewEventProcessor(eventCollector, conf.GetHex(), tsize)

Module-Specific Init Extensions:

Concrete modules extend initialization with their own setup. For example, MOpenSSLProbe.Init():

user/module/probe_openssl.go:109-176 adds:

  • Connection tracking map initialization (pidConns, sock2pidFd)
  • Capture mode selection (Text/Pcap/Keylog)
  • File handle creation for keylog/pcap files
  • Clock synchronization for timestamp conversion
  • SSL version offset map initialization

Sources: user/module/imodule.go:110-171, user/module/probe_openssl.go:109-176

Start Phase

The Start() method loads and attaches eBPF programs. This is where module-specific hooking logic executes.

Start Phase Operations:

  1. Manager Setup: Configure ebpfmanager.Manager with probes and maps
  2. Bytecode Selection: Choose appropriate .o file based on version detection
  3. Program Loading: Parse ELF bytecode and create eBPF programs
  4. Constant Editing: Inject runtime values (target PID, UID) into eBPF constants
  5. Program Attachment: Attach uprobes/kprobes/TC hooks to target functions
  6. Map Registration: Store references to eBPF maps for event reading

OpenSSL Module Start Example:

user/module/probe_openssl.go:280-350 demonstrates the flow:

1. setupManagers() - Configure manager based on capture mode (Text/Pcap/Keylog)
2. geteBPFName() - Select bytecode file (core vs non-core, version-specific)
3. assets.Asset(bpfFileName) - Load embedded bytecode
4. bpfManager.InitWithOptions() - Initialize with bytecode and options
5. bpfManager.Start() - Attach all programs
6. initDecodeFun() - Register event decoders for each eBPF map

Manager Configuration by Mode:

ModeManager SetupMaps UsedTC Hooks
TextsetupManagersText()eventsNo
PcapsetupManagersPcap()events, mastersecret_events, skb_eventsYes
KeylogsetupManagersKeylog()mastersecret_eventsNo

Sources: user/module/probe_openssl.go:280-350

Run Phase

The Run() method starts the event processing pipeline, launching goroutines for event reading and dispatching.

Run Phase Components:

Diagram: Run Phase Goroutine Architecture

Event Reading Implementation:

user/module/imodule.go:285-306 iterates over Events() maps and creates readers:

Each reader runs in a goroutine that:

  1. Blocks on rd.Read()
  2. Decodes raw bytes via child.Decode(em, record.RawSample)
  3. Dispatches events via Dispatcher(evt)
  4. Monitors context cancellation

Error Handling:

  • Lost samples logged with record.LostSamples count
  • Reader errors sent to errChan
  • Decode errors logged but don't stop processing
  • Context cancellation cleanly exits reader loops

Sources: user/module/imodule.go:236-262, user/module/imodule.go:285-391

Stop and Cleanup Phase

The Close() method performs graceful shutdown, releasing all acquired resources.

Cleanup Sequence:

  1. Set Closed Flag: isClosed.Store(true) prevents new event dispatch user/module/imodule.go:451
  2. Close Event Readers: Stop perf/ring buffer readers user/module/imodule.go:453-457
  3. Stop EventProcessor: Flush pending events and close workers user/module/imodule.go:458-459
  4. Detach eBPF Programs: Unload programs and maps via bpfManager.Stop() user/module/probe_openssl.go:352-358
  5. Close File Handles: Flush and close keylog/pcap files
  6. Cleanup Maps: Clear connection tracking and master key maps

Module Close Example (OpenSSL):

user/module/probe_openssl.go:352-358:

m.logger.Info().Msg("module close.")
if err := m.bpfManager.Stop(manager.CleanAll); err != nil {
    return fmt.Errorf("couldn't stop manager %w .", err)
}
return m.Module.Close()  // Call parent cleanup

Parent Module Close:

user/module/imodule.go:450-460:

m.isClosed.Store(true)  // Prevent new event dispatch
for _, iClose := range m.reader {
    if err := iClose.Close(); err != nil {
        return err
    }
}
err := m.processor.Close()  // Flush remaining events
return err

Sources: user/module/imodule.go:450-460, user/module/probe_openssl.go:352-358

Base Module Implementation

The Module struct provides common functionality shared across all capture modules, implementing the IModule interface partially and delegating specialized operations to child modules.

Shared Capabilities:

CapabilityImplementationPurpose
BTF DetectionautoDetectBTF()Determine CO-RE support user/module/imodule.go:173-190
Bytecode SelectiongeteBPFName()Choose _core.o vs _noncore.o variant user/module/imodule.go:191-214
Event ReadingperfEventReader(), ringbufEventReader()Read from eBPF maps user/module/imodule.go:308-391
Event DecodingDecode()Deserialize eBPF events user/module/imodule.go:393-406
Event RoutingDispatcher()Route events by type user/module/imodule.go:408-448
Output Encodingoutput()Format as text or protobuf user/module/imodule.go:461-479
Context Managementrun()Monitor for shutdown signals user/module/imodule.go:268-283

Parent-Child Delegation Pattern:

The base Module stores a reference to the concrete implementation via child IModule. Operations that require module-specific logic are delegated:

user/module/imodule.go:393-406 shows delegation in Decode():

es, found := m.child.DecodeFun(em)  // Get module-specific decoder
if !found {
    return error
}
te := es.Clone()  // Clone event struct
err = te.Decode(b)  // Deserialize

Initialization Flow with Delegation:

user/module/probe_openssl.go:109-176:

func (m *MOpenSSLProbe) Init(...) error {
    err = m.Module.Init(ctx, logger, conf, ecw)  // Call parent
    if err != nil {
        return err
    }
    m.Module.SetChild(m)  // Register self as child
    
    // Module-specific initialization
    m.eventMaps = make([]*ebpf.Map, 0, 2)
    m.eventFuncMaps = make(map[*ebpf.Map]event.IEventStruct)
    ...
}

Sources: user/module/imodule.go:83-108, user/module/imodule.go:110-171, user/module/probe_openssl.go:109-176

Module Configuration Integration

Modules receive configuration through the IConfig interface, allowing runtime behavior customization. Each module type has a corresponding config struct.

Config to Module Mapping:

ModuleConfig TypeCLI CommandKey Flags
MOpenSSLProbeOpensslConfigtls--libssl, --model, --pcapfile
MGoTLSProbeGoTLSConfiggotls--elfpath, --model
MGnuTLSProbeGnutlsConfiggnutls--gnutls, --ssl_version
MBashProbeBashConfigbash--bash, --errnumber
MMysqldProbeMysqldConfigmysqld--mysqld, --offset
MPostgresProbePostgresConfigpostgres--postgres

Global Config Application:

cli/cmd/root.go:156-175 applies global settings to module configs:

func setModConfig(globalConf config.BaseConfig, modConf config.IConfig) {
    modConf.SetPid(globalConf.Pid)
    modConf.SetUid(globalConf.Uid)
    modConf.SetDebug(globalConf.Debug)
    modConf.SetHex(globalConf.IsHex)
    modConf.SetBTF(globalConf.BtfMode)
    modConf.SetPerCpuMapSize(globalConf.PerCpuMapSize)
    modConf.SetTruncateSize(globalConf.TruncateSize)
    ...
}

Runtime Configuration Updates:

The HTTP server supports runtime config reloads via POST /config cli/cmd/root.go:375-396:

  1. New config received on reRloadConfig channel
  2. mod.Close() called to stop current module
  3. goto reload jumps back to initialization
  4. New config applied and module restarted

Sources: cli/cmd/root.go:156-175, cli/cmd/root.go:336-397, user/config/iconfig.go:24-70

Event Dispatch and Processing

The Dispatcher() method routes decoded events to appropriate handlers based on event type. This implements a three-way routing strategy.

Diagram: Event Dispatch Routing Logic

Event Type Categories:

user/module/imodule.go:408-448 routes events by type:

  • TypeOutput: Final formatted events ready for display → eventCollector.Write()
  • TypeEventProcessor: Events requiring aggregation/parsing → processor.Write()
  • TypeModuleData: Module-specific data (connections, master secrets) → child.Dispatcher()

Module-Specific Dispatch Example:

user/module/probe_openssl.go:733-754 handles OpenSSL-specific events:

func (m *MOpenSSLProbe) Dispatcher(eventStruct event.IEventStruct) {
    switch ev := eventStruct.(type) {
    case *event.ConnDataEvent:
        if ev.IsDestroy == 0 {
            m.AddConn(ev.Pid, ev.Fd, ev.Tuple, ev.Sock)  // Track connection
        } else {
            m.DelConn(ev.Sock)  // Remove connection
        }
    case *event.MasterSecretEvent:
        m.saveMasterSecret(ev)  // Write to keylog
    case *event.TcSkbEvent:
        m.dumpTcSkb(ev)  // Write to PCAP
    case *event.SSLDataEvent:
        m.dumpSslData(ev)  // Process plaintext data
    }
}

Sources: user/module/imodule.go:408-448, user/module/probe_openssl.go:733-754

Complete System Integration Flow

This diagram shows how modules integrate with the broader eCapture system from CLI invocation through event output.

Diagram: Complete Module Integration Sequence

Sources: cli/cmd/root.go:249-403, user/module/imodule.go:236-262

Module List and Capabilities

The following table summarizes all available modules and their key characteristics:

ModuleName ConstantPrimary TargeteBPF ProgramsEvent TypesOutput Modes
MOpenSSLProbeopenssllibssl.so, libcrypto.soUprobes (SSL_read/write), TCSSLDataEvent, MasterSecretEvent, TcSkbEvent, ConnDataEventText, PCAP, Keylog
MGoTLSProbegotlsGo crypto/tlsUprobes (crypto/tls funcs), TCTlsDataEvent, MasterSecretEvent, TcSkbEventText, PCAP, Keylog
MGnuTLSProbegnutlslibgnutls.soUprobes (gnutls_record funcs), TCSSLDataEvent, MasterSecretEventText, PCAP, Keylog
MNSSProbensprlibnspr4.soUprobes (PR_Read/Write)NsprDataEventText
MBashProbebash/bin/bashUprobes (readline funcs)BashEventText
MZshProbezsh/bin/zshUprobes (readline funcs)ZshEventText
MMysqldProbemysqldmysqld binaryUprobes (dispatch_command)MysqldEventText
MPostgresProbepostgrespostgres binaryUprobes (PostgresMain)PostgresEventText

Sources: Module files across user/module/ directory

Module System and Lifecycle has loaded