Skip to content

架构

本文档描述了 eCapture 的整体架构,解释了系统如何分层组织,以及数据如何从命令行接口通过 eBPF 探针流向最终输出。该架构遵循五层设计:CLI 层 → 模块编排 → eBPF 执行 → 事件处理 → 输出

有关特定捕获模块(OpenSSL、GoTLS 等)的详细信息,请参阅捕获模块。有关 eBPF 实现的信息,请参阅 eBPF 引擎。有关事件处理内部机制,请参阅事件处理流程


系统概述

eCapture 被组织为一个基于 eBPF 的模块化捕获系统。该架构将关注点分离到不同的层,允许在不修改核心基础设施的情况下添加新的捕获模块。每个模块都实现 IModule 接口并管理自己的 eBPF 程序,同时共享通用的事件处理和输出机制。

来源: README.md:36-44, cli/cmd/root.go:44-51, user/module/imodule.go:47-75


五层架构

架构概览:五个不同的层次,关注点明确分离

该架构由五个主要层次组成:

  1. CLI 层:解析命令和标志,管理配置
  2. 模块编排层:实现 IModule 接口模式,协调模块生命周期
  3. eBPF 执行层:加载和管理 eBPF 程序,将探针附加到目标函数
  4. 事件处理层:聚合原始 eBPF 事件并解析为结构化数据
  5. 输出层:格式化已处理的事件并写入各种目标

来源: cli/cmd/root.go:80-133, user/module/imodule.go:47-75, user/module/probe_openssl.go:83-106


CLI 层

CLI 层使用 Cobra 框架实现,为所有 eCapture 操作提供入口点。

CLI 命令流程:从用户输入到模块执行

cli/cmd/root.go:81-113 中的 rootCmd 是根 Cobra 命令。它定义了适用于所有子命令的全局标志:

标志类型用途默认值
--pid / -puint64目标进程 ID(0 = 所有进程)0
--uid / -uuint64目标用户 ID(0 = 所有用户)0
--debug / -dbool启用调试日志false
--btf / -buint8BTF 模式(0=自动,1=core,2=non-core)0
--mapsizeint每个 CPU 的 eBPF map 大小(KB)1024
--logaddr / -lstring日志输出地址""
--listenstringHTTP API 监听地址"localhost:28256"

每个子命令(例如 tlsgotlsbash)最终都会调用 cli/cmd/root.go:250-403 中的 runModule(),该函数:

  1. 使用 setModConfig() 从全局配置创建模块特定配置 cli/cmd/root.go:157-175
  2. 初始化日志记录器和事件收集器 cli/cmd/root.go:282-295
  3. 启动 HTTP 服务器用于运行时配置更新 cli/cmd/root.go:313-322
  4. 通过 IModule.Init() 初始化模块 cli/cmd/root.go:352-356
  5. 通过 IModule.Run() 运行模块 cli/cmd/root.go:358-362
  6. 处理重载或关闭信号 cli/cmd/root.go:367-396

来源: cli/cmd/root.go:80-154, cli/cmd/root.go:157-175, cli/cmd/root.go:250-403


模块编排层

模块编排层围绕 IModule 接口构建,所有捕获模块都实现该接口。

IModule 接口及其实现

user/module/imodule.go:47-75 中的 IModule 接口为所有捕获模块定义了契约:

  • Init(context.Context, *zerolog.Logger, config.IConfig, io.Writer) error:使用上下文、日志记录器、配置和事件写入器初始化模块
  • Name() string:返回模块名称
  • Start() error:启动 eBPF 程序并附加探针
  • Run() error:开始从 eBPF map 读取事件
  • Events() []*ebpf.Map:返回包含事件的 eBPF map
  • DecodeFun(*ebpf.Map) (event.IEventStruct, bool):返回特定 map 的解码函数
  • Dispatcher(event.IEventStruct):处理和路由解码后的事件
  • Close() error:清理资源

user/module/imodule.go:83-108 中的 Module 基类提供通用功能:

来源: user/module/imodule.go:47-108, user/module/imodule.go:236-262, user/module/imodule.go:285-391


模块生命周期

模块生命周期遵循三阶段模式:Init → Run → Close

模块生命周期:三阶段初始化、执行和清理

Init 阶段

Init() 方法执行模块初始化:

  1. 上下文和日志记录器设置user/module/imodule.go:111-127
  2. BTF 检测 使用 autoDetectBTF()user/module/imodule.go:173-190
  3. 内核版本检查user/module/imodule.go:140-149
  4. EventProcessor 创建user/module/imodule.go:127
  5. 子模块特定初始化(例如,OpenSSL 版本检测在 user/module/probe_openssl.go:109-176

Run 阶段

Run() 方法协调执行:

  1. 调用子模块的 Start()user/module/imodule.go:239-242
  2. 启动事件读取协程user/module/imodule.go:256-259
  3. 启动 EventProcessoruser/module/imodule.go:249-254
  4. 从 eBPF map 读取事件user/module/imodule.go:285-305

Start() 方法(由子模块实现)加载并附加 eBPF 程序:

  1. 根据捕获模式设置管理器user/module/probe_openssl.go:284-300
  2. 从嵌入资源加载字节码user/module/probe_openssl.go:310-326
  3. 初始化 bpfManageruser/module/probe_openssl.go:320-326
  4. 启动 bpfManager(附加探针)在 user/module/probe_openssl.go:328-331
  5. 初始化解码函数user/module/probe_openssl.go:333-347

Close 阶段

Close() 方法执行清理:

  1. 停止 bpfManager 并分离探针在 user/module/probe_openssl.go:352-357
  2. 关闭 EventProcessoruser/module/imodule.go:458-459
  3. 关闭事件读取器user/module/imodule.go:453-457

来源: 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 执行层

eBPF 执行层管理 eBPF 程序的加载、初始化和生命周期。

eBPF 程序加载和附加

字节码选择

eCapture 根据以下因素使用不同的 eBPF 字节码文件:

  1. 目标库版本:OpenSSL 1.0.x、1.1.x、3.0.x、3.x、BoringSSL 变体 user/module/probe_openssl.go:178-278
  2. CO-RE 支持:内核 BTF 可用性决定 CO-RE 或 non-CO-RE 字节码 user/module/imodule.go:173-190
  3. 内核版本:内核 < 5.2 有不同的限制 user/module/imodule.go:140-149

user/module/imodule.go:191-214 中的 geteBPFName() 方法通过在基础文件名后附加 _core.o_noncore.o 来选择合适的字节码文件。

管理器初始化

来自 ebpfmanager 库的 bpfManager 管理 eBPF 程序生命周期:

  1. 从嵌入资源加载字节码 通过 assets.Asset() user/module/probe_openssl.go:312-317
  2. 使用 InitWithOptions() 初始化管理器 user/module/probe_openssl.go:320-326
  3. 启动管理器 以使用 Start() 附加探针 user/module/probe_openssl.go:328-331

bpfManagerOptions 结构包含:

事件 Map

每个模块定义用于事件收集的 eBPF map:

来源: 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


事件处理层

事件处理层聚合原始 eBPF 事件、缓冲载荷并解析协议数据。详细信息请参阅事件处理流程

事件处理:聚合、缓冲和解析

事件解码

从 eBPF map 的原始字节解码为事件结构:

  1. 通过 DecodeFun() 获取解码器函数 user/module/imodule.go:228-230
  2. 通过 Decode() 将字节解码为事件结构 user/module/imodule.go:393-406
  3. 通过 Dispatcher() 分发事件 user/module/imodule.go:408-448

事件处理器

user/module/imodule.go:127 中的 EventProcessor 管理 worker 池:

  • 基于 UUID 的路由:具有相同 UUID(连接 ID)的事件发送到同一个 worker
  • Worker 生命周期:Worker 按需创建,在空闲后销毁
  • 缓冲累积:Worker 在解析之前累积事件片段

详见事件处理流程以了解实现细节。

来源: user/module/imodule.go:285-448, user/module/probe_openssl.go:741-783


输出层

输出层格式化处理后的事件并将其写入配置的目标。

输出格式化和目标

输出格式选择

输出格式由 eventCollector 写入器类型决定:

格式在 user/module/imodule.go:461-479Module.output() 中应用:

if m.eventOutputType == codecTypeProtobuf {
    // 编组为 protobuf
    le := new(pb.LogEntry)
    le.LogType = pb.LogType_LOG_TYPE_EVENT
    ep := e.ToProtobufEvent()
    ...
} else {
    // 转换为字符串
    s := e.String()
    ...
}

输出目标

输出目标通过 --logaddr--eventaddr 标志配置:

目标类型标志格式实现
Stdout(默认)(无)zerolog.ConsoleWriteros.Stdout
文件/path/to/file.logos.Create() 文件句柄
TCPtcp://host:portnet.Dial("tcp", addr)
WebSocketws://host:port/pathws.NewClient().Dial()

cli/cmd/root.go:178-247 中的日志记录器初始化根据地址格式创建适当的写入器。

模块特定输出

某些模块具有专门的输出模式:

详见输出格式以了解每种格式的详细信息。

来源: user/module/imodule.go:111-127, user/module/imodule.go:461-479, cli/cmd/root.go:178-247, user/config/iconfig.go:73-79


数据流总结

通过架构的完整数据流:

  1. 用户执行 CLI 命令rootCmd.Execute() 解析标志
  2. 子命令处理程序 使用模块名称和配置调用 runModule()
  3. 模块初始化IModule.Init() 检测库,选择字节码
  4. 模块启动IModule.Run() 加载 eBPF,附加探针,启动事件处理器
  5. eBPF 探针 在内核中捕获数据,写入 map
  6. 事件读取器 轮询 map,将字节解码为事件结构
  7. 分发器 将事件路由到事件处理器或模块特定处理程序
  8. 事件处理器 聚合片段,缓冲载荷,解析协议
  9. 输出格式化器 转换为文本或 protobuf
  10. 写入器 发送到 stdout、文件、TCP 或 WebSocket

该架构提供:

  • 模块化:新模块实现 IModule 而无需更改核心代码
  • 灵活性:多种输出格式和目标
  • 性能:使用 worker 池进行异步事件处理
  • 可扩展性:协议解析器和输出写入器是可插拔的

来源: cli/cmd/root.go:250-403, user/module/imodule.go:236-262, user/module/imodule.go:285-448

架构 has loaded