- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
本文是 Zeek 内置 Supervisor 框架核心 API 的权威技术指南,内容以 api.zeek 的官方 API 文档(api.zeek.rst)为主体骨架,并深入其脚本层实现与 C++ 底层源码。读完本文,你将掌握:如何使用Supervisor::create拉起持久的 Zeek 子进程、如何通过NodeConfig/ClusterEndpoint编排一个完整集群、如何用事件与钩子接管子进程的标准输出,以及如何借助 Broker 远程控制 Supervisor 进程树。
Supervisor 框架为 Zeek 引入了一种全新的运行模式:由一个"监督者"进程(Supervisor)管理一组应当长期存活的 Zeek 子进程,任何进程意外退出都会被自动复活,整个进程树在 Supervisor 退出时有序关闭。该 API 自 Zeek 3.1.0 引入,并在 4.0.0 起趋于稳定;在此之前它可能在不经通知的情况下发生不兼容变更。本文所有代码示例均来自当前仓库的真实脚本,可直接复制运行。
Supervisor API 概览:命名空间与组成
Supervisor API 全部位于Supervisor命名空间下,共分为四类构件:
| 类别 | 成员 | 说明 |
|---|---|---|
| 类型(Types) | Supervisor::ClusterEndpoint、Supervisor::ClusterRole、Supervisor::NodeConfig、Supervisor::NodeStatus、Supervisor::Status | 描述被监督节点及其集群角色、当前状态 |
| 事件(Events) | Supervisor::node_status | 节点(重新)启动成功时的通知 |
| 钩子(Hooks) | Supervisor::stdout_hook、Supervisor::stderr_hook | 接管所有子进程的标准输出/错误流 |
| 函数(Functions) | Supervisor::create、Supervisor::destroy、Supervisor::is_supervised、Supervisor::is_supervisor、Supervisor::node、Supervisor::restart、Supervisor::status | 节点生命周期管理与运行环境查询 |
脚本层的类型与函数声明定义在 scripts/base/frameworks/supervisor/api.zeek,实现则位于 scripts/base/frameworks/supervisor/main.zeek;底层与 C++ 引擎交互的 BIF 桥接层见 src/supervisor/supervisor.bif。
核心类型详解
ClusterRole:节点在集群框架中的角色
Supervisor::ClusterRole是一个枚举,定义了被监督节点在 Zeek Cluster Framework 中将要扮演的角色,取值如下:
| 枚举值 | 含义 |
|---|---|
Supervisor::NONE | 不参与集群,作为独立节点运行 |
Supervisor::LOGGER | 集群日志记录节点 |
Supervisor::MANAGER | 集群管理节点 |
Supervisor::PROXY | 集群代理节点 |
Supervisor::WORKER | 集群工作节点(通常负责抓包与分析) |
从源码看,该枚举定义于 api.zeek,并同步映射到 BIF 层(supervisor.bif),供 C++ 侧识别角色。
ClusterEndpoint:集群中某个节点的端点描述
Supervisor::ClusterEndpoint是一个 record,描述集群布局中一个被监督节点的端点信息(注意它描述的是节点配置,而非单条连接):
| 字段 | 类型 | 必填/可选 | 说明 |
|---|---|---|---|
role | ClusterRole | 必填 | 该集群节点扮演的角色 |
host | addr | 必填 | 集群节点运行的主机/IP |
p | port | 必填 | 集群节点监听的 TCP 端口 |
interface | string | &optional | 节点读取/分析数据包的网络接口名,通常由 worker 节点使用 |
pcap_file | string | &optional | 节点读取/分析数据包的 PCAP 文件名,通常由 worker 节点使用 |
metrics_port | port | &optional | 节点向 Prometheus 暴露指标的 TCP 端口 |
NodeConfig:被监督节点的完整配置
Supervisor::NodeConfig是create 调用最核心的参数,完整控制被监督节点的行为:
| 字段 | 类型 | 默认值/可选 | 说明 |
|---|---|---|---|
name | string | 必填 | 节点名称,在给定监督进程树内唯一,通常人类可读 |
interface | string | &optional | 节点读取/分析数据包的网络接口名 |
pcap_file | string | &optional | 节点读取/分析数据包的 PCAP 文件名 |
directory | string | &optional | 节点使用的工作目录 |
stdout_file | string | &optional | 节点 stdout 重定向到的文件路径 |
stderr_file | string | &optional | 节点 stderr 重定向到的文件路径 |
bare_mode | bool | &optional | 是否以 bare 模式启动节点;省略时继承 Supervisor 自身的 bare-mode 状态 |
addl_base_scripts | vector of string | &default = [] | 在 base 脚本之后、任何用户指定脚本之前加载的附加脚本 |
addl_user_scripts | vector of string | &default = [] | 在用户指定脚本之后加载的附加脚本 |
env | table[string] of string | &default = {} | 在节点中定义的环境变量 |
cpu_affinity | int | &optional | 节点尝试将自己绑定到的 CPU/核编号 |
cluster | table[string] of ClusterEndpoint | &default = {} | 集群布局定义,键为节点名 |
其中cluster字段值得特别说明:集群框架中的每个节点都知晓自己所归属的完整、静态的集群拓扑。当 Supervisor 孵化被监督节点时,会自动把这个表翻译成正确的 Cluster Framework 配置——例如同时填充CLUSTER_NODE环境变量和Cluster::nodes表(参见 api.zeek 的注释)。在 C++ 侧,该字段被序列化为 JSON 字符串保存(见 Supervisor.h)。
NodeStatus 与 Status:状态查询结果
Supervisor::NodeStatus:单个被监督节点的当前状态,含两个字段——node: NodeConfig(期望的节点配置)与pid: int &optional(节点当前或最后已知的进程 ID,进程尚未启动时可能未初始化)。Supervisor::Status:一组被监督节点的状态,仅含一个字段nodes: table[string] of NodeStatus,以节点名为键。
两者定义于 api.zeek,是Supervisor::status函数的返回类型。
生命周期管理函数
Supervisor命名空间下共 7 个函数,其中create、destroy、restart、status四个只能由 Supervisor 进程调用(从其他进程调用是错误用法)。
create:孵化新的被监督节点
function Supervisor::create(node: NodeConfig): string传入期望的节点配置,返回空字符串表示成功,否则返回错误/失败描述。官方示例中典型的调用方式:
local sn = Supervisor::NodeConfig($name="foo", $interface="en0"); local res = Supervisor::create(sn); if ( res == "" ) print "supervisor created a new node"; else print "supervisor failed to create node", res;destroy / restart / status:节点销毁、重启与状态查询
function Supervisor::destroy(node: string &default=""): bool function Supervisor::restart(node: string &default=""): bool function Supervisor::status(node: string &default=""): Status三个函数都接受节点名作为参数,且都支持**空字符串表示"全部节点"**这一特殊语义:
destroy:销毁并移除被监督节点,成功返回true。restart:通过先销毁(kill)再重新创建的方式重启节点,成功返回true。status:返回当前状态——空串时返回所有节点的Status,指定名字时返回该节点的状态。
is_supervisor / is_supervised / node:进程角色判定
这三个函数用于让同一份脚本在 Supervisor 进程与子进程中都正确运行:
is_supervisor(): bool—— 当前进程是否为 Supervisor 进程。is_supervised(): bool—— 当前进程是否为被监督节点进程。node(): NodeConfig—— 若当前进程是被监督节点,返回其节点配置;从非被监督进程调用是错误。
值得注意:由Supervisor::create孵化的子进程会继承 Supervisor 进程加载的脚本(命令行参数也基本全部继承,唯一不继承的是-r(读 PCAP)与-i(实时接口)这两个参数)。因此脚本作者常用is_supervisor()与is_supervised()区分角色分支,用node()$name进一步区分多个子进程。
事件与钩子:感知节点状态、接管输出流
node_status 事件
event Supervisor::node_status(node: string, pid: count)Supervisor 在收到来自 stem 进程的状态消息更新、确认某个节点已(重新)启动时触发。node是通过Supervisor::create创建过的节点名,pid是 stem 上报的进程 ID。该事件在 main.zeek 中实现:收到后立即通过 Broker 发布到SupervisorControl::topic_prefix + "/node_status",实现远程通知。
stdout_hook 与 stderr_hook
hook Supervisor::stdout_hook(node: string, msg: string): bool hook Supervisor::stderr_hook(node: string, msg: string): bool这两个钩子接管所有子进程(包括内部 stem 进程)的 stdout/stderr 行缓冲输出。关键语义:
node:消息来源的节点名(此前通过create创建);空值表示消息来自内部 supervisor stem 进程(这种情况通常不应发生)。msg:子进程 stdout/stderr 的行缓冲内容。- 若钩子以
break结束,将抑制该行输出到关联流(stdout/stderr)。
例如希望过滤某个节点的调试噪音,可以这样注册:
hook Supervisor::stdout_hook(node: string, msg: string) { if ( node == "noisy-node" && /DEBUG/ in msg ) break; # 抑制该行 }钩子声明见 api.zeek。
实战一:监督一个抓包节点
官方框架文档(doc/frameworks/supervisor.rst)给出了最简用法:simple-supervisor.zeek:
event zeek_init() { if ( Supervisor::is_supervisor() ) { local sn = Supervisor::NodeConfig($name="foo", $interface="en0"); local res = Supervisor::create(sn); if ( res == "" ) print "supervisor created a new node"; else print "supervisor failed to create node", res; } else print fmt("supervised node '%s' zeek_init()", Supervisor::node()$name); } event zeek_done() { if ( Supervisor::is_supervised() ) print fmt("supervised node '%s' zeek_done()", Supervisor::node()$name); else print "supervisor zeek_done()"; }运行方式:
$ zeek -j simple-supervisor.zeek其中-j是开启 Supervisor 模式的关键命令行参数(详见 supervisor.rst)。本地测试时请把en0换成真实可嗅探的接口名;若接口启用了校验和卸载(checksum offloading),可加-C忽略校验和:
$ zeek -j -C simple-supervisor.zeek可以看到,这份脚本在 Supervisor 进程和子进程中都被加载执行,靠is_supervisor()/is_supervised()分支;子进程侧再用Supervisor::node()$name打印自己的节点名。Supervisor 退出时,整个进程树会有序关闭,子进程的zeek_done()同样会被触发。
实战二:用 Supervisor 编排完整集群
cluster-supervisor.zeek 展示了如何一键拉起 manager、logger、proxy、worker 四类节点:
event zeek_init() { if ( ! Supervisor::is_supervisor() ) return; Broker::listen("127.0.0.1", 9999/tcp); local cluster: table[string] of Supervisor::ClusterEndpoint; cluster["manager"] = [$role=Supervisor::MANAGER, $host=127.0.0.1, $p=10000/tcp]; cluster["logger"] = [$role=Supervisor::LOGGER, $host=127.0.0.1, $p=10001/tcp]; cluster["proxy"] = [$role=Supervisor::PROXY, $host=127.0.0.1, $p=10002/tcp]; cluster["worker"] = [$role=Supervisor::WORKER, $host=127.0.0.1, $p=10003/tcp, $interface="en0"]; for ( n, ep in cluster ) { local sn = Supervisor::NodeConfig($name=n); sn$cluster = cluster; sn$directory = n; if ( ep?$interface ) sn$interface = ep$interface; local res = Supervisor::create(sn); if ( res != "" ) print fmt("supervisor failed to create node '%s': %s", n, res); } }要点拆解:
- 用
Supervisor::ClusterEndpoint记录构造cluster表,键为节点名,值为端点信息(角色、地址、端口、接口)。 - 每个节点创建独立的
NodeConfig:name取自节点名,cluster填入完整拓扑(集群框架要求每个节点都知道完整静态拓扑),directory设为节点名对应的子目录,使各节点工作目录隔离。 - worker 节点的接口名从
ClusterEndpoint的interface字段传递到NodeConfig。 Broker::listen("127.0.0.1", 9999/tcp)让 Supervisor 监听自己的端口,供外部远程控制(见下文)。- 子进程的 stdout/stderr 输出会自动经由 Supervisor 转发,并加上节点名前缀,便于区分来源(见 supervisor.rst)。
运行:
$ zeek -j cluster-supervisor.zeek实战三:通过 Broker 远程控制 Supervisor
Supervisor 进程除了提供本地 API,还能作为 Broker 端点接收远程指令。远程控制 API 定义在SupervisorControl命名空间(control.zeek),包含两个可重定义选项:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
SupervisorControl::topic_prefix | string&redef | "zeek/supervisor" | 订阅 Supervisor API 请求、发布响应所用的 Broker topic 前缀 |
SupervisorControl::enable_listen | bool&redef | F | 启用后,Supervisor 将监听配置的Broker::default_listen_address |
以及一组成对事件:create_request/create_response、status_request/status_response、restart_request/restart_response、destroy_request/destroy_response、stop_request(无响应,Supervisor 收到即终止自身进程树)、node_status(Supervisor::node_status的远程等价物)。
官方控制端示例 supervisor-control.zeek:
event zeek_init() { Broker::peer("127.0.0.1", 9999/tcp, 1sec); } event Broker::peer_added(endpoint: Broker::EndpointInfo, msg: string) { Broker::publish(SupervisorControl::topic_prefix, SupervisorControl::restart_request, "", ""); } event SupervisorControl::restart_response(reqid: string, result: bool) { print fmt("got result of supervisor restart request: %s", result); terminate(); }运行:
$ zeek supervisor-control.zeek它会连接到 9999 端口上的 Supervisor,建立 Broker 对等关系后发布restart_request(reqid与node均为空串,含义是"重启全部节点"——适合改完脚本后热重载整个集群)。请求中的reqid是任意字符串,会被原样回显到响应中,用于关联请求与响应。
在 main.zeek 中可以看到请求分发的机制:zeek_init时若is_supervisor()且enable_listen则Broker::listen(),随后Cluster::subscribe(SupervisorControl::topic_prefix)订阅控制 topic。每个*_request事件处理函数都会调用对应的本地 API,并把结果发布回topic_prefix/<操作>/<reqid>主题。stop_request处理最简单——直接terminate()关闭整个进程树(main.zeek)。
内部架构:Supervisor → Stem → 被监督节点
理解 API 背后的进程模型,有助于合理预估资源占用与故障行为。官方文档(supervisor.rst)明确说明:顶层 Supervisor 进程并不直接管理被监督节点,而是先孵化一个中间进程Stem,由 Stem 负责被监督节点的生命周期。这样设计有两个原因:
- 避免对子进程执行
exec()——否则每次孵化都依赖文件系统上当时的zeek二进制版本,系统升级期间可能引发兼容性/竞态问题。唯一仍需要exec()的情形是 Stem 进程本身意外死亡(属于罕见场景),此时 Supervisor 依据Supervisor::Config::zeek_exe_path(记录在 Supervisor.h)重新 fork+exec 出 Stem。 - Zeek 运行时普遍污染全局状态,尽早
fork()出 Stem 相当于保留一份纯净的基线镜像,供所有被监督进程复用。
因此实际存在两级监督:Supervisor 复活 Stem,Stem 复活自己的子节点。此外,Stem 和任何被监督子进程都会自动检测"是否与父进程失联"并自我终止:Stem 每秒钟从其poll()循环醒来检查父 PID 是否变化;被监督节点则通过一个周期性Timer(ParentProcessCheckTimer,见 Supervisor.cc)完成同样检查。除失联检查与配置获取方式外,被监督节点运行期与普通 Zeek 进程并无差异。
节点复活策略:指数退避
supervisor.rst 与 Supervisor.cc 共同确认了节点复活(Node Revival)的确切算法,三个硬编码常量如下:
constexpr auto attempts_before_delay_increase = 3; // 每个延迟档位最多尝试 3 次 constexpr auto delay_increase_factor = 2; // 每档结束后延迟翻倍 constexpr auto reset_revival_state_after = 30; // 节点存活满 30 秒后重置复活状态行为归纳:
- 从1 秒延迟开始,Stem 尝试复活节点,最多 3 次;
- 此后复活延迟翻倍(2 秒、4 秒、8 秒……),每档再试最多 3 次,如此无限继续——Stem永不放弃任何节点,但延迟持续增长;
- 一旦节点存活超过 30 秒,复活状态清零,下次意外退出时重新从 1 秒延迟开始。
也就是说,频繁崩溃的节点不会被高频轰炸式重启,而是逐渐降低重启频率;稳定的节点则始终获得快速恢复。
BIF 层与 C++ 桥接
脚本 API 最终由 src/supervisor/supervisor.bif 桥接到 C++ 引擎(zeek::supervisor_mgr)。几个值得注意的实现事实:
__status/__create/__destroy/__restart在zeek::supervisor_mgr为空时(即未以-j启动 Supervisor 模式)会报错"supervisor mode not enabled",create 此时返回该错误字符串——这与"从非 Supervisor 进程调用是错误"的文档语义一致。__is_supervisor直接判断zeek::supervisor_mgr != nullptr;__is_supervised判断Supervisor::ThisNode().has_value();__node从ThisNode()->config.ToRecord()还原脚本层的NodeConfigrecord。- 脚本层的
NodeConfig在 C++ 侧对应Supervisor::NodeConfig结构体(Supervisor.h),并通过FromRecord/ToRecord/ToJSON/FromJSON在脚本层 record 与 JSON 之间转换——这为未来进程间传递配置提供了序列化基础。 - BIF 中还有一个未出现在脚本 API 文档的
Supervisor::__stem_pid():Supervisor 进程返回 stem PID,被监督节点返回父 PID,可用于调试进程树关系(supervisor.bif)。
使用限制与注意事项
- 调用主体限制:
create、destroy、restart、status只能由 Supervisor 进程调用;node只能由被监督节点调用。混用会导致运行期错误。 - API 稳定性:该 API 自 3.1.0 引入,在 4.0.0 之前视为不稳定,可能不经弃用警告直接不兼容变更。
- 参数继承:子进程继承 Supervisor 的大多数命令行参数,但
-r(PCAP)与-i(实时接口)不继承,必须在NodeConfig中显式指定interface/pcap_file。 - 命名唯一性:节点名在给定监督进程树内必须唯一;
destroy/restart/status的空串参数会作用于全部节点,操作前请确认意图。 - 远程控制:若要使用
SupervisorControl的远程事件,需在 Supervisor 侧显式Broker::listen(...)(如集群示例中的 9999 端口),否则外部无法连接;enable_listen选项默认关闭。 - 输出重定向:
stdout_file/stderr_file与stdout_hook/stderr_hook都可用于处理子进程输出——前者重定向到文件,后者在脚本内接管;钩子以break结束可抑制对应行。
延伸阅读
- API 完整文档:api.zeek.rst | 远程控制文档:control.zeek.rst
- 框架概念与架构说明:doc/frameworks/supervisor.rst
- 脚本层实现:main.zeek | control.zeek | api.zeek
- 引擎层实现:Supervisor.cc | Supervisor.h | supervisor.bif
- 官方示例:simple-supervisor.zeek | cluster-supervisor.zeek | supervisor-control.zeek
- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
相关推荐
Zeek Supervisor 框架实战指南:进程监督、节点复活与集群部署
Zeek Supervisor 框架实战指南:进程监督、节点复活与集群部署 导读 Supervisor 是 Zeek 内置的一套进程监督框架(位于 src/su
网络安全网络IDSSupervisor XML-RPC API 完全指南:supervisord 远程控制、进程管理与日志读取接口详解
Supervisor XML RPC API 完全指南:supervisord 远程控制、进程管理与日志读取接口详解 导读 本文是 Supervisor(sup
运维Zeek 控制框架(Control Framework)详解:基于 Broker 的远程运行时控制与信息采集
Zeek 控制框架(Control Framework)详解:基于 Broker 的远程运行时控制与信息采集 Control framework 是 Zeek
网络安全网络IDS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考