Skip to content

模块系统与生命周期

本文档解释了 eCapture 的模块系统架构,包括 IModule 接口规范、模块注册模式,以及从初始化到关闭的完整生命周期。所有捕获模块(TLS、Bash、MySQL 等)都实现了 IModule 接口并遵循标准生命周期:Init → Start → Run → Close。

关于模块如何配置自身的详细信息,请参阅第 2.4 节(配置系统)。关于模块内 eBPF 程序加载的信息,请参阅第 2.1 节(eBPF 引擎)。关于捕获后的事件处理,请参阅第 2.2 节(事件处理流程)。

IModule 接口规范

IModule 接口定义了所有模块必须实现的标准规范。该接口支持整个系统中的多态模块处理,允许 CLI 和 HTTP API 以统一方式实例化和管理不同的捕获类型(TLS、Bash、MySQL 等)。

核心接口定义

IModule 接口在 user/module/imodule.go:47-75 中定义,并规定了所有模块必须实现的契约:

方法返回类型目的调用方
Init(context.Context, *zerolog.Logger, config.IConfig, io.Writer) errorerror使用依赖项初始化模块cli/cmd/root.go:352 中的 runModule()
Name() stringstring返回模块标识符("openssl"、"gotls" 等)日志记录和错误消息
Run() errorerror启动事件处理管道cli/cmd/root.go:358 中的 runModule()
Start() errorerror加载 eBPF 字节码并附加程序user/module/imodule.go:239Module.Run() 调用
Stop() errorerror优雅地停止捕获(保留供将来使用)当前未使用
Close() errorerror释放所有资源并清理cli/cmd/root.go:387 中的 runModule()
SetChild(IModule)-注册具体实现(父子模式)模块自身的 Init()
Events() []*ebpf.Map[]*ebpf.Map返回要读取事件的 eBPF 映射user/module/imodule.go:293 中的 readEvents()
DecodeFun(*ebpf.Map) (event.IEventStruct, bool)(event.IEventStruct, bool)获取特定映射的事件解码器user/module/imodule.go:394 中的 Decode()
Decode(*ebpf.Map, []byte) (event.IEventStruct, error)(event.IEventStruct, error)反序列化原始 eBPF 事件字节user/module/imodule.go:341-345 中的事件读取器协程
Dispatcher(event.IEventStruct)-将解码的事件路由到处理程序user/module/imodule.go:348 中的事件读取器协程

来源: user/module/imodule.go:47-75

图示:IModule 接口和类层次结构

父子模式允许基础 Module 结构提供通用功能(通过 autoDetectBTF() 进行 BTF 检测,通过 perfEventReader()ringbufEventReader() 读取事件,通过 run() 进行上下文管理),同时通过存储在 Module 中的 child 字段将模块特定操作(通过 Start() 设置 eBPF 程序,通过 DecodeFun() 解码事件)委托给具体实现。

来源: user/module/imodule.go:83-108user/module/probe_openssl.go:83-106

模块注册与工厂模式

eCapture 使用工厂模式进行模块实例化,根据 CLI 命令动态加载模块。每个模块在包初始化期间注册一个工厂函数。

注册机制

图示:模块注册和工厂模式流程

每个模块在 Go 包初始化期间注册一个工厂函数。当 CLI 调用命令(例如 ecapture tls)时,runModule() 函数在 moduleRegistry 中查找工厂并创建实例。

来源: user/module/probe_openssl.go:785-794cli/cmd/root.go:344-347

模块名称常量和 CLI 映射

每个模块由模块源文件中定义的唯一字符串常量标识。这些常量用作 moduleRegistry 映射的键。

CLI 命令模块名称常量工厂函数目标
ecapture tlsModuleNameOpenssl"openssl"NewOpenSSLProbe()OpenSSL/BoringSSL (libssl.so)
ecapture gotlsModuleNameGotls"gotls"NewGoTLSProbe()Go crypto/tls 包
ecapture gnutlsModuleNameGnutls"gnutls"NewGnuTLSProbe()GnuTLS (libgnutls.so)
ecapture nsprModuleNameNspr"nspr"NewNsprProbe()NSS/NSPR (libnspr4.so)
ecapture bashModuleNameBash"bash"NewBashProbe()Bash shell
ecapture zshModuleNameZsh"zsh"NewZshProbe()Zsh shell
ecapture mysqldModuleNameMysqld"mysqld"NewMysqldProbe()MySQL 服务器
ecapture postgresModuleNamePostgres"postgres"NewPostgresProbe()PostgreSQL

CLI 命令到 runModule() 调用:

来源: cli/cmd/tls.go:62-67cli/cmd/gotls.go:52-58cli/cmd/bash.go:53-55cli/cmd/mysqld.go:47-49

模块生命周期阶段

模块生命周期由五个不同的阶段组成,每个阶段都有特定的职责。模块按顺序经过这些阶段,在每个转换点都有错误处理。

图示:带代码引用的模块生命周期状态机

来源: cli/cmd/root.go:336-397user/module/imodule.go:110-171user/module/imodule.go:236-262user/module/probe_openssl.go:109-176user/module/probe_openssl.go:280-350user/module/probe_openssl.go:352-358

初始化阶段(Init)

Init() 方法由 cli/cmd/root.go:352 中的 runModule() 调用,执行依赖注入和环境设置。

Module.Init() 基础职责(user/module/imodule.go:111-171):

操作代码引用目的
设置 isClosed 标志imodule.go:112使用原子布尔值跟踪关闭状态
存储 ctximodule.go:113从 CLI 继承取消上下文
存储 loggerimodule.go:114模块特定的 zerolog 实例
创建 errChanimodule.go:115用于异步错误的缓冲通道
BTF 检测imodule.go:154-169调用 autoDetectBTF() 或使用配置
内核版本检查imodule.go:140-148kernel.HostVersion() < 5.2 检测
设置 eventOutputTypeimodule.go:120-126选择文本或 protobuf 编码
创建 EventProcessorimodule.go:127event_processor.NewEventProcessor(ecw, hex, tsize)
启动错误处理器协程imodule.go:129-139processor.ErrorChan() 读取

BTF 自动检测逻辑(user/module/imodule.go:173-190):

func (m *Module) autoDetectBTF() {
    isContainer, err := ebpfenv.IsContainer()
    if isContainer {
        m.logger.Warn().Msg("检测到容器,BTF 自动检测可能失败")
    }
    enable, e := ebpfenv.IsEnableBTF()
    if enable {
        m.isCoreUsed = true  // 使用 CO-RE 字节码(_core.o)
    }
}

模块特定的 Init 扩展

具体模块调用 m.Module.Init(),然后添加自己的设置。来自 MOpenSSLProbe.Init() 的示例 user/module/probe_openssl.go:109-176

操作代码引用目的
调用父 Initprobe_openssl.go:111m.Module.Init(ctx, logger, conf, ecw)
注册子模块probe_openssl.go:116m.Module.SetChild(m) 用于委托
初始化映射probe_openssl.go:117-122eventMapseventFuncMapspidConnssock2pidFdmasterKeys
选择捕获模式probe_openssl.go:128-154根据 conf.Model 选择 Text/Pcap/Keylog
打开输出文件probe_openssl.go:131-148创建 keylogger 或 pcap 文件句柄
同步时钟probe_openssl.go:156-167unix.ClockGettime(CLOCK_MONOTONIC) 用于时间戳转换
初始化 SSL 偏移映射probe_openssl.go:172initOpensslOffset() 用于版本到字节码的映射

来源: user/module/imodule.go:110-171user/module/probe_openssl.go:109-176cli/cmd/root.go:352

Start 阶段

Start() 方法由 user/module/imodule.go:239 中的 Module.Run() 调用,将 eBPF 程序加载到内核中。

MOpenSSLProbe.Start() 流程(user/module/probe_openssl.go:280-350

图示:MOpenSSLProbe 的 Start 阶段序列

按捕获模式的管理器设置:

eBPFProgramType设置函数使用的 eBPF 映射TC 钩子Uprobes
TlsCaptureModelTypeTextsetupManagersText()eventsSSL_read/write
TlsCaptureModelTypePcapsetupManagersPcap()eventsmastersecret_eventsskb_events是(egress/ingress)SSL_read/write、SSL_in_before
TlsCaptureModelTypeKeylogsetupManagersKeylog()mastersecret_events仅 SSL_in_before

eBPF 程序的常量编辑(user/module/probe_openssl.go:361-395

constantEditor() 方法将运行时值注入 eBPF 全局变量:

常量名称值来源目的
target_pidconf.GetPid()按进程 ID 过滤事件(0 = 所有)
target_uidconf.GetUid()按用户 ID 过滤事件(0 = 所有)
less52m.isKernelLess5_2在内核 < 5.2 上禁用 PID/UID 过滤

来源: user/module/probe_openssl.go:280-350user/module/probe_openssl.go:361-395user/module/imodule.go:239

Run 阶段

Run() 方法由 cli/cmd/root.go:358 中的 runModule() 调用,启动用于事件处理的并发协程。

Run 阶段架构

图示:带代码引用的 Run 阶段协程架构

事件读取器实现细节

user/module/imodule.go:285-306 中的 readEvents() 方法遍历 child.Events() 返回的所有 eBPF 映射,并根据映射类型创建适当的读取器:

Perf Event Array 读取器(user/module/imodule.go:308-351

操作代码行描述
创建读取器perf.NewReader(em, m.conf.GetPerCpuMapSize()) imodule.go:310分配每 CPU 环形缓冲区
启动协程go func() { ... }() imodule.go:316每个映射一个协程
读取事件record, err := rd.Read() imodule.go:326从环形缓冲区阻塞读取
检查丢失样本if record.LostSamples != 0 imodule.go:335记录缓冲区溢出
解码m.child.Decode(em, record.RawSample) imodule.go:341将字节反序列化为事件结构
分发m.Dispatcher(evt) imodule.go:348路由到事件处理程序
检查取消case <-m.ctx.Done() imodule.go:320在上下文取消时退出

Ring Buffer 读取器(user/module/imodule.go:353-391

类似的流程,但在 imodule.go:354 处使用 ringbuf.NewReader(em) 而不是 perf 读取器。在较新的内核(5.8+)上,Ring Buffer 因其更好的性能而优先使用。

来源: user/module/imodule.go:236-262user/module/imodule.go:285-391cli/cmd/root.go:358

Stop 和清理阶段

Close() 方法由 cli/cmd/root.go:387 中的 runModule() 在关闭期间调用,按初始化的相反顺序执行资源清理。

清理序列

图示:模块清理序列

MOpenSSLProbe.Close() 实现(user/module/probe_openssl.go:352-358

go
func (m *MOpenSSLProbe) Close() error {
    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()  // 委托给父类
}

Module.Close() 实现(user/module/imodule.go:450-460

步骤代码目的
1m.isClosed.Store(true)原子地设置关闭标志以防止来自 Dispatcher() 的新事件
2for _, iClose := range m.reader遍历 perf/ring buffer 读取器
3iClose.Close()停止读取器协程(导致 Read() 返回错误)
4m.processor.Close()刷新聚合的事件并关闭工作器池
5return err传播任何错误

额外的模块特定清理

某些模块在调用 Module.Close() 之前执行额外的清理:

  • MOpenSSLProbe:如果在 keylog 模式下,关闭 keylogger 文件句柄
  • MTCProbe(MOpenSSLProbe 的父类):关闭 pcapng 写入器,刷新 DSB(解密密钥块)
  • 连接跟踪映射(pidConnssock2pidFd)被垃圾回收

来源: user/module/imodule.go:450-460user/module/probe_openssl.go:352-358cli/cmd/root.go:387

基础 Module 实现

user/module/imodule.go:83-108 处的 Module 结构实现了所有捕获模块使用的通用操作:

Module 基类中的共享功能

功能方法实现位置目的
BTF 检测autoDetectBTF()imodule.go:173-190检测内核是否支持用于 CO-RE 字节码的 BTF
字节码文件选择geteBPFName(filename string) stringimodule.go:191-214根据 BTF 支持添加 _core.o_noncore.o 后缀
事件读取(Perf)perfEventReader(errChan, em)imodule.go:308-351ebpf.PerfEventArray 映射创建 perf 读取器协程
事件读取(Ring)ringbufEventReader(errChan, em)imodule.go:353-391ebpf.RingBuf 映射创建 ring buffer 读取器协程
事件解码Decode(em *ebpf.Map, b []byte)imodule.go:393-406使用 child.DecodeFun(em) 反序列化字节
事件路由Dispatcher(e event.IEventStruct)imodule.go:408-448根据 e.EventType() 路由到输出、处理器或子模块
输出编码output(e event.IEventStruct)imodule.go:461-479编码为文本字符串或 protobuf LogEntry
生命周期监控run()imodule.go:268-283监控 ctx.Done()errChan 以获取关闭信号
读取器管理readEvents()imodule.go:285-306遍历 child.Events() 并生成读取器

父子委托模式

Module 基类使用组合和委托来共享通用代码,同时允许模块特定的自定义。child IModule 字段存储对具体模块(例如 MOpenSSLProbe)的引用。

委托的工作原理

图示:Decode 和 Dispatcher 的委托流程

SetChild 注册模式

每个具体模块在初始化期间调用 SetChild(m) 来注册自身:

来自 MOpenSSLProbe.Init() 的示例(user/module/probe_openssl.go:109-176

go
func (m *MOpenSSLProbe) Init(ctx context.Context, logger *zerolog.Logger, 
                             conf config.IConfig, ecw io.Writer) error {
    // 1. 首先调用父类 Init
    err = m.Module.Init(ctx, logger, conf, ecw)
    if err != nil {
        return err
    }
    
    // 2. 注册自身为子模块以进行委托
    m.Module.SetChild(m)  // 第 116 行
    
    // 3. 模块特定的初始化
    m.eventMaps = make([]*ebpf.Map, 0, 2)
    m.eventFuncMaps = make(map[*ebpf.Map]event.IEventStruct)
    m.pidConns = make(map[uint32]map[uint32]ConnInfo)
    // ... 更多设置
}

此模式允许父类 Module 回调子类的方法,如 Start()Events()DecodeFun()Dispatcher()

来源: user/module/imodule.go:393-406user/module/imodule.go:408-448user/module/probe_openssl.go:109-176

模块配置集成

模块通过 IConfig 接口接收配置,允许运行时行为自定义。每个模块类型都有相应的配置结构。

配置到模块的映射:

模块配置类型CLI 命令关键标志
MOpenSSLProbeOpensslConfigtls--libssl--model--pcapfile
MGoTLSProbeGoTLSConfiggotls--elfpath--model
MGnuTLSProbeGnutlsConfiggnutls--gnutls--ssl_version
MBashProbeBashConfigbash--bash--errnumber
MMysqldProbeMysqldConfigmysqld--mysqld--offset
MPostgresProbePostgresConfigpostgres--postgres

全局配置应用:

cli/cmd/root.go:156-175 将全局设置应用于模块配置:

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)
    ...
}

运行时配置更新:

HTTP 服务器通过 POST /config 支持运行时配置重新加载 cli/cmd/root.go:375-396

  1. reRloadConfig 通道上接收新配置
  2. 调用 mod.Close() 停止当前模块
  3. goto reload 跳回初始化
  4. 应用新配置并重新启动模块

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

事件分发和处理

Dispatcher() 方法根据事件类型将解码的事件路由到适当的处理程序。这实现了三路路由策略。

图示:事件分发路由逻辑

事件类型类别:

user/module/imodule.go:408-448 按类型路由事件:

  • TypeOutput:准备显示的最终格式化事件 → eventCollector.Write()
  • TypeEventProcessor:需要聚合/解析的事件 → processor.Write()
  • TypeModuleData:模块特定数据(连接、主密钥) → child.Dispatcher()

模块特定的 Dispatcher 实现

每个具体模块实现 Dispatcher() 来处理其特定的事件类型。基础 Module.Dispatcher()TypeModuleData 事件调用 child.Dispatcher()

MOpenSSLProbe.Dispatcher() 实现(user/module/probe_openssl.go:741-762

go
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)
        } else {
            m.DelConn(ev.Sock)
        }
    case *event.MasterSecretEvent:
        m.saveMasterSecret(ev)
    case *event.MasterSecretBSSLEvent:
        m.saveMasterSecretBSSL(ev)
    case *event.TcSkbEvent:
        err := m.dumpTcSkb(ev)
        if err != nil {
            m.logger.Error().Err(err).Msg("save packet error.")
        }
    case *event.SSLDataEvent:
        m.dumpSslData(ev)
    }
}

MOpenSSLProbe 的事件类型处理程序映射:

事件类型处理方法目的
*event.ConnDataEventAddConn() / DelConn()跟踪套接字 FD 到元组的映射
*event.MasterSecretEventsaveMasterSecret()将 TLS 1.2/1.3 密钥写入 keylog 文件
*event.MasterSecretBSSLEventsaveMasterSecretBSSL()将 BoringSSL 密钥写入 keylog 文件
*event.TcSkbEventdumpTcSkb()将数据包写入 PCAP 文件
*event.SSLDataEventdumpSslData()查找连接信息并写入 processor

连接跟踪方法:

来源: user/module/imodule.go:408-448user/module/probe_openssl.go:741-762user/module/probe_openssl.go:406-488

完整系统集成流程

此序列图展示了从 CLI 命令执行到模块初始化、eBPF 程序附加、事件捕获和清理的完整生命周期。

图示:从 CLI 到内核的完整模块集成序列

关键集成点:

阶段入口点关键操作
CLI 解析cobra.Command.RunE解析标志,调用 runModule()
模块创建runModule() root.go:344-351GetModuleFunc() → 工厂函数 → 模块实例
初始化mod.Init() root.go:352父类 Module.Init() → 子类特定初始化 → SetChild()
eBPF 加载mod.Run() root.go:358Start()setupManagers()bpfManager.InitWithOptions()bpfManager.Start()
事件捕获Run() 中的协程readEvents()perfEventReader()Decode()Dispatcher()
事件处理processor.Serve()工作器池按 UUID 聚合事件,解析协议
清理mod.Close() root.go:387bpfManager.Stop() → 分离程序 → 关闭读取器 → 刷新处理器

来源: cli/cmd/root.go:249-403cli/cmd/tls.go:62-67user/module/imodule.go:236-262user/module/probe_openssl.go:280-350

模块列表和功能

下表总结了所有可用模块及其关键特性:

模块名称常量主要目标eBPF 程序事件类型输出模式
MOpenSSLProbeopenssllibssl.so、libcrypto.soUprobes (SSL_read/write)、TCSSLDataEvent、MasterSecretEvent、TcSkbEvent、ConnDataEventText、PCAP、Keylog
MGoTLSProbegotlsGo crypto/tlsUprobes (crypto/tls 函数)、TCTlsDataEvent、MasterSecretEvent、TcSkbEventText、PCAP、Keylog
MGnuTLSProbegnutlslibgnutls.soUprobes (gnutls_record 函数)、TCSSLDataEvent、MasterSecretEventText、PCAP、Keylog
MNSSProbensprlibnspr4.soUprobes (PR_Read/Write)NsprDataEventText
MBashProbebash/bin/bashUprobes (readline 函数)BashEventText
MZshProbezsh/bin/zshUprobes (readline 函数)ZshEventText
MMysqldProbemysqldmysqld 二进制Uprobes (dispatch_command)MysqldEventText
MPostgresProbepostgrespostgres 二进制Uprobes (PostgresMain)PostgresEventText

来源: user/module/ 目录中的模块文件

模块系统与生命周期 has loaded