- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
Broker 集群后端是 Zeek 集群框架中历史最悠久、使用最广泛的点对点通信后端:它自 Bro 2.6 起投入使用,并作为默认后端一直持续到 Zeek 8.1。本文以 Broker 集群后端官方文档 为骨架,结合仓库中的 main.zeek 实现 与 Cluster 框架核心,系统讲解其固定对等连接策略、基于 cluster-layout.zeek 的集群布局定义、节点启动时的监听/订阅/连接流程,以及基于hello/node_up/node_down事件的节点生命周期管理。读完本文,你将能够理解并独立配置一套基于 Broker 的 Zeek 集群。
Broker 后端的定位:从默认走向可选
Broker 集群后端(policy/frameworks/cluster/backend/broker)是 Zeek 集群框架(base/frameworks/cluster)的一个可选后端实现。它的定位在文档中表述得非常明确:
- 它是一种点对点(peer-to-peer)后端,集群中每个节点按照固定的连接策略主动与特定类型的其他节点建立 Broker 对等连接;
- 它自Bro 2.6起就在 Zeek 中使用,并一直是默认后端,直到Zeek 8.1才让位于新的后端实现(backend 目录 中同时存在
broker与zeromq两个后端); - 集群节点的对等关系信息存储在
Cluster::nodes表中,该表由cluster-layout.zeek文件填充,或在启用 Supervisor 时由 Supervisor 内部填充。
从源码上看,选择后端的机制非常简单:仓库中的 main.zeek 在加载时直接对Cluster::backend进行重定义:
module Cluster; redef Cluster::backend = Cluster::CLUSTER_BACKEND_BROKER;而 init-bare.zeek 中定义了后端的默认值为Cluster::CLUSTER_BACKEND_NONE。也就是说,默认情况下集群后端是"未启用"的;只有通过@load frameworks/cluster/backend/broker(或@load frameworks/cluster/backend/zeromq)显式加载某个后端,或者由上层工具自动注入后,集群框架才真正具备通信能力。这一点在 main.zeek 中有强制校验:一旦设置了Cluster::node但Cluster::backend仍为NONE,Zeek 会直接以 fatal 错误退出,提示必须选择一个集群后端。
固定对等连接策略:三句话概括整个拓扑
Broker 后端的连接拓扑完全由节点类型决定,不需要额外的手工配置。官方文档用三句话概括了这一策略:
- 所有节点都与所有 logger 节点建立对等连接;
- 所有 worker 节点都与所有 proxy 节点以及 manager 节点建立对等连接;
- 所有 proxy 节点都与 manager 建立对等连接。
这三条规则意味着:logger、manager 和 proxy 节点必须监听 cluster layout 中定义的端口(它们是被连接方),而 worker 只作为主动连接方。对照源码 main.zeek 的zeek_init分派逻辑,可以看得一清二楚:
switch ( self$node_type ) { case MANAGER: connect_peers_with_type(LOGGER); break; case PROXY: connect_peers_with_type(LOGGER); if ( self?$manager ) connect_peer(MANAGER, self$manager); break; case WORKER: connect_peers_with_type(LOGGER); connect_peers_with_type(PROXY); if ( self?$manager ) connect_peer(MANAGER, self$manager); break; }可见连接规则是严格按节点类型分派的:
| 节点类型 | 主动连接的对象 |
|---|---|
| MANAGER | 所有 LOGGER 节点 |
| PROXY | 所有 LOGGER 节点 + 自己配置的 manager 节点 |
| WORKER | 所有 LOGGER 节点 + 所有 PROXY 节点 + 自己配置的 manager 节点 |
| LOGGER | 不主动连接任何节点,只负责监听 |
文档中没有单独列出 LOGGER 的连接行为,但从switch分派可以看出:LOGGER 节点不在任何case中,因此它只监听端口、接收来自所有节点的入站对等连接。这与"所有节点都与所有 logger 节点互连"的规则完全对应——logger 是被动连接方。
值得强调的是,worker 与 worker 之间永远不会建立直接连接,proxy 与 proxy 之间也不会。这正是文档后半部分特别提示的发布-订阅可见性限制的来源(见下文)。
集群布局:cluster-layout.zeek 与 Cluster::nodes
Broker 后端的连接行为完全依赖于Cluster::nodes中定义的集群布局。cluster 框架文档 对布局文件提出了明确要求:需要有一个名为cluster-layout.zeek的脚本存在于 Zeek 的脚本搜索路径(ZEEKPATH)中,其中定义Cluster::nodes变量;同时通过CLUSTER_NODE环境变量(或Cluster::node)指定当前实例扮演的角色,并加载@load base/frameworks/cluster。
⚠️ 官方文档特别警告:cluster-layout.zeek只应包含Cluster::nodes的定义,避免加载其他 Zeek 脚本或对除Cluster::nodes以外的任何变量使用redef——因为该文件加载时机非常早,很容易引发循环加载问题(main.zeek)。
Node 记录结构与节点类型
每个节点的配置是一个 Node 记录:
type Node: record { node_type: NodeType; # 节点类型 ip: addr; # 节点 IP zone_id: string &default=""; # 非全局 IPv6 地址的 zone id p: port &default=0/unknown; # 监听端口;0/unknown 表示不预配置监听 manager: string &optional; # worker/proxy 使用的 manager 节点名 id: string &optional; # Broker 框架分配的节点标识,连接期间才存在 metrics_port: port &optional; # Prometheus 指标端口 };其中NodeType枚举定义了六种节点类型(types.zeek):NONE(非集群运行)、CONTROL(可查看/操作其他节点配置)、LOGGER(负责日志管理)、MANAGER(负责策略管理)、PROXY(中继 worker 通信并同步 worker 状态)、WORKER(实际执行流量分析)。
一个可运行的布局示例
仓库的测试基础设施中保存了一份完整的 测试用 cluster-layout.zeek,它展示了如何通过环境变量动态构建一个含 manager、logger、proxy、worker 的多节点布局。其核心写法如下:
@load frameworks/cluster/backend/broker # 仅配置 manager 时的最小布局 redef Cluster::nodes = { ["manager"] = [$node_type=Cluster::MANAGER, $ip=127.0.0.1, $p=to_port(getenv("BROKER_MANAGER_PORT"))], }; # 根据环境变量是否设置,动态追加节点 @if ( getenv("BROKER_LOGGER1_PORT") != "" ) redef Cluster::manager_is_logger = F; redef Cluster::nodes += { ["logger-1"] = [$node_type=Cluster::LOGGER, $ip=127.0.0.1, $p=to_port(getenv("BROKER_LOGGER1_PORT")), $manager="manager"], }; @endif @if ( getenv("BROKER_WORKER1_PORT") != "" ) redef Cluster::nodes += { ["worker-1"] = [$node_type=Cluster::WORKER, $ip=127.0.0.1, $p=to_port(getenv("BROKER_WORKER1_PORT")), $manager="manager"], }; @endif这份文件揭示了布局配置的几个关键约定:
- manager 字段:每个 worker/proxy 节点必须通过
$manager="manager"指向自己的 manager 节点名,connect_peer()正是依据该字段定位 manager 的 IP 和端口; - manager_is_logger:当集群中没有独立 logger 节点、由 manager 兼任 logger 时,应设置
redef Cluster::manager_is_logger = T;(其默认值在 main.zeek 中即为T,并注明"仅当Cluster::nodes中没有 logger 时才应为真",ZeekControl 会自动处理该值); - 端口来源:测试中端口来自
@TEST-PORT注入的环境变量(如BROKER_WORKER1_PORT),生产环境中则通常是固定端口或由 ZeekControl/systemd-generator 生成; $p = 0/unknown的含义:根据 types.zeek 注释,端口为0/unknown表示该节点不预先配置监听——这在 main.zeek 中体现为跳过Broker::listen()调用。
Cluster::nodes表本身定义于 集群框架核心,以节点名(如"manager"、"worker-1")为索引。
节点启动流程:zeek_init 中的三件事
Broker 后端在zeek_init()事件中完成全部启动工作(main.zeek),该处理器被赋予&priority=-10。源码注释解释了优先级选择的深意:订阅设置处理器运行在优先级 -5,先于本处理器执行;而 -10 的低优先级同时允许用户在zeek_init()中临时改动 cluster-layout 用于测试。
启动流程按顺序分为三部分:
1. 前置检查
if ( getenv("ZEEKCTL_CHECK_CONFIG") != "" ) return; if ( ! Cluster::is_enabled() ) return; if ( Cluster::backend != Cluster::CLUSTER_BACKEND_BROKER ) return;ZEEKCTL_CHECK_CONFIG环境变量存在时直接跳过(这是 ZeekControl 做配置检查时的通道);Cluster::is_enabled()的实现非常朴素:return (node != "")(main.zeek),即CLUSTER_NODE环境变量(对应Cluster::node)非空即视为集群运行;- 后端类型不匹配(例如同时加载了其他后端)则直接返回,保证互斥。
2. 日志订阅与监听
switch ( self$node_type ) { case LOGGER: Cluster::subscribe(Broker::default_log_topic_prefix); break; case MANAGER: if ( Cluster::manager_is_logger ) Cluster::subscribe(Broker::default_log_topic_prefix); break; } if ( self$p != 0/unknown ) { Broker::listen(Broker::default_listen_address, self$p, Broker::default_listen_retry); Cluster::log(fmt("listening on %s:%s", Broker::default_listen_address, self$p)); }- 订阅规则:任何处理日志的节点额外订阅
Broker::default_log_topic_prefix——LOGGER 节点无条件订阅;MANAGER 仅在manager_is_logger为真(即 manager 兼任 logger)时订阅。该前缀的默认值为"zeek/logs/"(broker/main.zeek)。 - 监听规则:只要布局中给本节点定义了端口(
$p != 0/unknown),就调用Broker::listen()。监听地址使用Broker::default_listen_address,其默认值来自ZEEK_DEFAULT_LISTEN_ADDRESS环境变量(broker/main.zeek),重试间隔使用Broker::default_listen_retry。这印证了文档"logger、manager 和 proxy 节点都在监听 cluster layout 定义的端口"的论断——只有这些被连接方需要$p。
3. 按节点类型发起对等连接
如上一节表格所示,manager/proxy/worker 分别调用connect_peers_with_type(LOGGER)、connect_peer(MANAGER, self$manager)等函数完成出站连接。这两个函数是后端连接的核心实现(main.zeek):
function connect_peer(node_type: NodeType, node_name: string) { local nn = nodes_with_type(node_type); for ( i in nn ) { local n = nn[i]; if ( n$name != node_name ) next; if ( ! hook connect_node_hook(n) ) return; local status = Broker::peer(cat(n$node$ip), n$node$p, Cluster::retry_interval); Cluster::log(fmt("initiate peering with %s:%s, retry=%s, status=%s", n$node$ip, n$node$p, Cluster::retry_interval, status)); return; } Reporter::warning(fmt("connect_peer: node '%s' (%s) not found", node_name, node_type)); }实现细节值得注意:
nodes_with_type()(定义于 集群框架核心)从Cluster::nodes中筛出指定类型的节点,并按名称排序返回,保证连接顺序确定;connect_node_hook是用户可干预的扩展点:这是 Cluster::connect_node_hook 钩子,仅对 Broker 后端生效,hook调用返回假(被 break)时该节点会被跳过,从而允许用户阻止特定连接建立;Broker::peer()是实际建立对等连接的调用,重试间隔使用Cluster::retry_interval——默认1sec,且可被ZEEK_DEFAULT_CONNECT_RETRY(单位为秒)环境变量覆盖(main.zeek);- 每次发起连接都会写入
Cluster::log流(对应cluster.log日志文件,流定义见 main.zeek); - 目标节点在布局中不存在时,会通过
Reporter::warning发出警告——这是排查布局拼写错误时非常有用的信号。
节点生命周期:hello / node_up / node_down
Broker 后端的节点发现完全基于事件驱动,由两个 Broker 事件和三个 Cluster 事件协同完成(main.zeek):
event Broker::peer_added(endpoint: Broker::EndpointInfo, msg: string) &priority=10 { if ( ! Cluster::is_enabled() ) return; local e = Broker::make_event(Cluster::hello, node, Cluster::node_id()); Broker::publish(nodeid_topic(endpoint$id), e); } event Broker::peer_lost(endpoint: Broker::EndpointInfo, msg: string) &priority=10 { for ( node_name, n in nodes ) { if ( n?$id && n$id == endpoint$id ) { event Cluster::node_down(node_name, endpoint$id); break; } } }完整的握手链路如下:
- 建连:节点 A 通过
Broker::peer()与节点 B 建立 Broker 对等连接; - 自我介绍:B 端触发
Broker::peer_added,随即通过Broker::publish把Cluster::hello事件发布到nodeid_topic(endpoint$id)——即"zeek/cluster/nodeid/<id>/"前缀对应的主题(前缀定义见 main.zeek,主题构造函数见 main.zeek)。Cluster::hello携带两个参数:节点名Cluster::node和节点标识Cluster::node_id()(默认即Broker::node_id,可被其他后端重定义); - 上报在线:收到
hello的对端在 Cluster::hello 处理器 中校验节点名是否存在于Cluster::nodes(不在则报错),记录节点id,首次见到该 id 时触发本地Cluster::node_up(name, id)事件,并将 id 登记进按节点类型分组的active_node_ids集合——该集合是Cluster::get_active_node_count()(main.zeek)的数据来源,manager 可据此统计各类型节点的实际存活数量; - 上报离线:连接断开时对端触发
Broker::peer_lost,在Cluster::nodes中按id反查节点名,触发Cluster::node_down(node_name, id);node_down 处理器 会清除节点的id并从active_node_ids移除该 id。
这套机制保证了节点标识(id)是短暂有效的:节点重启后重新连接时会获得新的 id,旧连接通过node_down清理,新连接通过hello/node_up重新登记。
发布-订阅可见性:直接对等连接是硬边界
官方文档在末尾特意强调了一个容易被忽略的重要限制:
publish-subscribe visibility with Broker is limited to nodes that are directly peered. A worker publishing a message to a topic another worker node is subscribed to will not be visible by the other worker.
即:Broker 的发布-订阅可见性仅限于直接互连的节点。由于 worker 之间不直接对等连接(见固定连接策略),一个 worker 向另一个 worker 订阅的主题发布消息,另一个 worker 是看不到的。如果业务逻辑依赖跨 worker 的 pub/sub 通信,必须通过 manager 或 proxy 中转,或者改用其他具备转发能力的后端。这个限制是固定拓扑策略的直接推论,也是设计集群内通信机制时务必牢记的前提。
相关配置参数速查
以下是 Broker 后端涉及的全部核心参数,均可在布局文件或节点配置中调整:
| 参数 | 默认值 | 作用 | 定义位置 |
|---|---|---|---|
Cluster::backend | CLUSTER_BACKEND_NONE | 选择集群后端 | init-bare.zeek |
Cluster::node | getenv("CLUSTER_NODE") | 当前节点名称 | cluster/main.zeek |
Cluster::nodes | {} | 集群布局表 | cluster/main.zeek |
Cluster::manager_is_logger | T | manager 是否兼任 logger | cluster/main.zeek |
Cluster::retry_interval | 1sec | 连接重试间隔(可被ZEEK_DEFAULT_CONNECT_RETRY覆盖) | cluster/main.zeek |
Broker::default_listen_address | getenv("ZEEK_DEFAULT_LISTEN_ADDRESS") | 监听地址 | broker/main.zeek |
Broker::default_log_topic_prefix | "zeek/logs/" | 日志主题前缀 | broker/main.zeek |
测试与验证
仓库的测试基础设施完整覆盖了 Broker 后端的启动与对等连接场景:
- 测试用 cluster-layout.zeek 展示了通过
@TEST-PORT环境变量注入端口的完整布局构建方式,可直接作为自定义集群布局的参考模板; - start-it-up.zeek 与 start-it-up-logger.zeek 验证了包含/不包含独立 logger 两种拓扑下集群的启动流程;
- topic_distribution.zeek 等测试覆盖了主题分发机制,可用于理解 pub/sub 路由在固定拓扑下的实际行为。
小结
Broker 集群后端是理解 Zeek 集群通信机制的理想起点:它的固定对等连接策略简单而明确,节点发现流程完全事件化,布局定义集中在一个cluster-layout.zeek文件中。掌握本文所述的三条连接规则、zeek_init启动三部曲(订阅、监听、连接)以及hello/node_up/node_down生命周期,你就能够读懂任何现有 Zeek 集群的 Broker 配置,也能为自己的集群编写正确、可维护的布局文件——同时时刻记得"发布-订阅可见性仅限直接互连节点"这一关键边界。
- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
相关推荐
Zeek 集群 Broker 后端(policy/frameworks/cluster/backend/broker)源码级解析:对等连接策略、背压处理与遥测指标
Zeek 集群 Broker 后端(policy/frameworks/cluster/backend/broker)源码级解析:对等连接策略、背压处理与遥测指
网络安全网络IDSZeek 集群 Broker 后端深度解析:节点互联策略、backpressure 与遥测监控
Zeek 集群 Broker 后端深度解析:节点互联策略、backpressure 与遥测监控 Broker 集群后端是 Zeek 集群框架中历史最悠久、使用最
网络安全网络IDSZeek Cluster 框架深度解析:集群布局、节点角色与核心 API 实战指南
Zeek Cluster 框架深度解析:集群布局、节点角色与核心 API 实战指南 导读 本文以 Zeek 仓库中集群框架的核心文档 doc/scripts/b
网络安全网络IDS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考