- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
base/frameworks/control是 Zeek 内置的远程控制框架,它借助 Broker 消息总线,为运行中的 Zeek 实例提供了一套"命令"机制:既可以在运行时远程修改实例的配置(如重定义&redef常量),也可以远程收集实例的运行信息(变量值、对端通信状态、网络统计等)。阅读本文后,你将掌握控制端(controller)与被控端(controllee)的完整运行模型、id_value/peer_status/net_stats/configuration_update/shutdown五种内建命令的用法,并能结合仓库源码与 btest 测试用例,在自己的 Zeek 环境中搭建可用的远程控制通道。
框架定位:为"运行时远程干预"提供基础
Zeek 官方的框架文档对它的定位非常明确:The control framework provides the foundation for providing "commands" that can be taken remotely at runtime to modify a running Zeek instance or collect information from the running instance.(见 doc/scripts/base/frameworks/control/index.rst)。也就是说,这个框架解决的核心问题不是入侵检测本身,而是运维侧的可控性——让一个已经跑起来的 Zeek 进程,可以被外部通过消息通道"指挥"。
它依赖两个关键基础设施:
- Broker 消息总线:所有控制指令和应答都以 Broker 事件的形式在节点之间交换;
- 集群(Cluster)通信节点配置:控制消息的路由目标(
Cluster::node)与发布(Cluster::publish)均建立在集群命名空间之上。
从代码结构看,框架被拆成了三层:
- 基础层:scripts/base/frameworks/control/main.zeek——定义模块、常量、命令集与全部事件签名;
- 被控端实现:scripts/policy/frameworks/control/controllee.zeek——加载到"被控制"的 Zeek 实例上,负责应答命令;
- 控制端实现:scripts/policy/frameworks/control/controller.zeek——一个一次性命令行工具,向远端发出命令并退出。
__load__.zeek只是简单地@load ./main(见 scripts/base/frameworks/control/load.zeek),因此直接加载基础框架并不会引入任何收发逻辑,真正的行为完全由后两个策略脚本决定。
核心模块 main.zeek:命令集与事件契约
scripts/base/frameworks/control/main.zeek 定义了module Control下的全部"合同",是理解整个框架的起点。
通信配置常量
| 常量 | 默认值 | 含义 |
|---|---|---|
Control::topic_prefix | "zeek/control" | 所有控制消息在 Broker 上使用的主题前缀 |
Control::controllee_listen | T(可 redef) | 被控端是否自行调用Broker::listen();集群模式下由集群初始化流程统一调用,无需重复监听 |
Control::host | 0.0.0.0(可 redef) | 被控主机的地址 |
Control::host_port | 0/tcp(可 redef) | 被控主机的端口 |
Control::zone_id | ""(可 redef) | 当Control::host是非全局 IPv6 地址、需要指定 RFC 4007 的zone_id时使用 |
Control::cmd | ""(可 redef) | 要执行的命令,通常在命令行上通过Control::cmd=...指定 |
Control::arg | ""(可 redef) | 需要参数的命令(如id_value)所携带的参数 |
内建命令集
const commands: set[string] = { "id_value", "peer_status", "net_stats", "configuration_update", "shutdown", } &redef;五种命令分别对应三类能力:读取信息(id_value、peer_status、net_stats)、修改配置(configuration_update)、生命周期管理(shutdown)。另有一个Control::ignore_ids: set[string] = { }常量,用于在配置同步时排除特定变量 ID。
事件对:请求 / 响应
框架采用"请求-响应"事件对设计,每个命令都有一对事件:
| 请求事件 | 响应事件 | 用途 |
|---|---|---|
Control::id_value_request(id: string) | Control::id_value_response(id: string, val: string) | 请求/返回某个变量 ID 的值 |
Control::peer_status_request() | Control::peer_status_response(s: string) | 请求/返回当前通信状态 |
Control::net_stats_request() | Control::net_stats_response(s: string) | 请求/返回网络统计 |
Control::configuration_update_request() | Control::configuration_update_response() | 请求/应答配置更新;Control::configuration_update是它的别名事件,也是框架的主要挂接点 |
Control::shutdown_request() | Control::shutdown_response() | 请求/应答关闭实例 |
main.zeek末尾还定义了一个辅助事件:
event terminate_event() &is_used { terminate(); }它把"结束进程"封装成可调度的事件,被控端在收到shutdown_request后会延迟调度它,控制端则在收到任何响应后调用它退出。
被控端 controllee.zeek:如何让一个 Zeek 实例可被远程控制
要让某个 Zeek 实例接受远程控制,只需在启动参数中追加控制端脚本(见 scripts/policy/frameworks/control/controllee.zeek 头部注释):
zeek <scripts> frameworks/control/controllee<scripts>是你想在该实例上运行的分析脚本集。同时,通信节点配置中需要有一个节点被配置为 controller 节点。
启动时的订阅与监听
event zeek_init() &priority=-10 { if ( Cluster::backend == Cluster::CLUSTER_BACKEND_BROKER ) { Broker::subscribe(Control::topic_prefix + "/" + Broker::node_id()); if ( Control::controllee_listen ) Broker::listen(); } }在zeek_init(优先级-10)时,被控端订阅zeek/control/<自身 node_id>主题——这是它接收指令的"信箱地址";若Control::controllee_listen为真则主动监听端口。注意集群后端必须是 Broker(CLUSTER_BACKEND_BROKER),且集群模式下初始化流程已经统一处理监听,因此controllee_listen在该场景下无需为真。
四种信息类命令的应答实现
id_value_request的应答最直观地展示了"远程读取变量"的机制:
event Control::id_value_request(id: string) { local val = lookup_ID(id); local reply_topic = Control::topic_prefix + "/id_value_response/" + Cluster::node; Cluster::publish(reply_topic, Control::id_value_response, id, fmt("%s", val)); }它调用 Zeek 解释器内置的lookup_ID()在当前全局作用域中查找变量 ID 并格式化其值,然后发布到zeek/control/id_value_response/<cluster node>主题。lookup_ID是核心解释器的公开接口,定义于 src/Scope.cc(const IDPtr& lookup_ID(const char* name, ...)),与脚本层事件处理一一对应。
peer_status_request遍历Broker::peers(),按行输出每个对端的网络时间、peer ID、地址与状态:
status += fmt("%.6f peer=%s host=%s status=%s\n", network_time(), bpeer$peer$id, bpeer$peer$network$address, bpeer$status);net_stats_request调用get_net_stats()获取收包统计,输出recvd、dropped、link三个计数:
local reply = fmt("%.6f recvd=%d dropped=%d link=%d\n", network_time(), ns$pkts_recvd, ns$pkts_dropped, ns$pkts_link);两个响应都发布到zeek/control/<cmd>_response/<Cluster::node>主题。
配置更新与关停
configuration_update_request的处理很关键——它并不自己做实质更新,而是触发别名事件并通知各脚本:
event Control::configuration_update_request() { event Control::configuration_update(); # ... 由其他脚本处理 redef-able const 的运行时修改 ... local topic = Control::topic_prefix + "/configuration_update_response/" + Cluster::node; Cluster::publish(topic, Control::configuration_update_response); }shutdown_request先回发确认事件,再延迟 1 秒调度terminate_event():
schedule 1sec { terminate_event() };延迟的目的在注释中写得很清楚:let the current event queue flush itself first,即先让当前事件队列排空,避免在事件处理中途强行终止导致丢失数据。
控制端 controller.zeek:一次性的远程命令客户端
scripts/policy/frameworks/control/controller.zeek 被设计为一个"发完命令即退出"的实用脚本,官方用法(见文件头部注释):
zeek <scripts> frameworks/control/controller \ Control::host=<host_addr> Control::host_port=<host_port> \ Control::cmd=<command> [Control::arg=<arg>]启动时的参数校验与建连
event zeek_init() &priority=5 { if ( cmd !in commands ) { Reporter::error(fmt("The '%s' control command is unknown.", cmd)); terminate(); } Broker::subscribe(Control::topic_prefix); Broker::peer(cat(host), host_port); }控制端在zeek_init(优先级5)先校验Control::cmd是否属于内建命令集,非法命令直接报错退出;随后订阅整个zeek/control主题(接收所有节点发回的响应),并调用Broker::peer(cat(host), host_port)与被控端建立对等连接。
命令派发:send_control_request
send_control_request()根据cmd将请求发布到指定主题:
case "id_value": if ( arg == "" ) Reporter::fatal("The Control::id_value command requires that Control::arg also has some value."); Broker::publish(topic, Control::id_value_request, arg); break; case "peer_status": Broker::publish(topic, Control::peer_status_request); break; case "net_stats": Broker::publish(topic, Control::net_stats_request); break; case "shutdown": Broker::publish(topic, Control::shutdown_request); break; case "configuration_update": Broker::publish(topic, Control::configuration_update_request); break;可以看到id_value是唯一必须携带Control::arg的命令,缺参时直接Reporter::fatal终止。
建连回调:peer_added 与配置同步
命令实际是在Broker::peer_added(优先级-10)中触发的,因为此时才确认到与被控端的连接已建立:
event Broker::peer_added(endpoint: Broker::EndpointInfo, msg: string) &priority=-10 { local topic = Control::topic_prefix + "/" + endpoint$id; if ( cmd == "configuration_update" ) { if ( ! Broker::enable_identifier_updates ) Reporter::fatal("Identifier updates are disabled."); local ids = configurable_ids(); for ( id in ids ) if ( Broker::publish_id(topic, id) ) ++publish_count; Reporter::info(fmt("Control framework sent %d IDs", publish_count)); } send_control_request(topic); }configuration_update走的是 Broker 的**标识符更新(identifier update)**机制:控制端通过Broker::publish_id()把被控端所有"可配置"的 ID 推送过去。因此,使用configuration_update前必须在被控端开启Broker::enable_identifier_updates(默认关闭),否则控制端会直接报错退出。
configurable_ids()函数给出了"可配置 ID"的精确筛选规则:
# Skip it if the variable isn't redefinable or not const. if ( t$constant && t$redefinable && t$type_name != "func" ) rval[id] = t;即只有&redef声明的const全局变量才会被同步;普通全局变量(通常是状态存储)被刻意排除,注释解释了原因:those values will frequently be declared with &redef so that attributes can be redefined;函数类型因序列化支持不完整而暂不发送。被列入Control::ignore_ids的 ID 也会被跳过。
控制端收到对应响应事件后(各响应 handler 均以优先级-10挂接),统一调用terminate_event()退出,完成"发出命令 → 收到应答 → 进程结束"的完整生命周期。
五种内建命令速查与实战组合
综合main.zeek的事件定义与控制/被控端实现,五种命令可总结为:
| 命令 | 需要参数 | 数据流向 | 典型用途 |
|---|---|---|---|
id_value | Control::arg=<变量 ID> | 控制端 → 被控端 → 控制端 | 远程读取任意全局变量的当前值 |
peer_status | 无 | 同上 | 查看被控端当前的 Broker 对端连接状态 |
net_stats | 无 | 同上 | 查看收包 / 丢包 / 链路统计 |
configuration_update | 无 | 控制端 → 被控端(推送全部可 redef 常量) | 运行时热更新配置 |
shutdown | 无 | 控制端 → 被控端(回发确认) | 优雅关停远端实例 |
一个读取变量值的完整调用示例:
zeek frameworks/control/controller \ Control::host=127.0.0.1 Control::host_port=5000/tcp \ Control::cmd=id_value Control::arg=test_var对应的被控端(假定运行在127.0.0.1:5000)会执行lookup_ID("test_var"),并把结果通过Control::id_value_response发布回控制端打印。
测试用例佐证:btest 中的端到端验证
仓库自带的 btest 测试直接给出了可复现的最小环境,是理解命令真实调用链的最佳范本(均位于 testing/btest/scripts/base/frameworks/control/):
- id_value.zeek:后台同时启动
controllee与controller两个进程,控制器以Control::cmd=id_value Control::arg=test_var运行。测试特意在被控端用redef test_var = "This is the value from the controllee"覆盖原值,并在控制端打印id_value_response,验证远程读取到的是被控端实例上的值,而非控制端自身脚本的值。 - configuration_update.zeek:被控端先
redef Broker::enable_identifier_updates = T;,控制端加载额外的test-redef.zeek脚本并以Control::cmd=configuration_update连接。被控端在zeek_init与zeek_done各打印一次test_var,通过两次输出的差异验证运行时配置同步确实生效。 - shutdown.zeek:控制端以
Control::cmd=shutdown运行,验证被控端能收到请求、回发shutdown_response并延迟终止。
这些测试还揭示了一个易被忽略的部署细节:id_value测试因涉及"多个 Zeek 进程同时运行"而不适用于-O gen-C++编译优化模式(见测试文件头部@TEST-REQUIRES声明),实战中若启用 ZAM/gen-C++ 编译,应确认控制场景仍按预期工作。
注意事项与适用边界
- Broker 是硬依赖:被控端仅在
Cluster::backend == Cluster::CLUSTER_BACKEND_BROKER时执行订阅/监听逻辑,因此该框架面向 Broker 后端集群或单机 Broker 环境。 - 集群模式差异:集群中由 setup 流程统一调用
Broker::listen(),故Control::controllee_listen通常无需为真;单机手动控制场景则保留默认T。 - 配置同步的前提条件:
configuration_update依赖 Broker 标识符更新特性,被控端必须显式redef Broker::enable_identifier_updates = T;,且只会同步&redef的const变量,函数与ignore_ids中的 ID 不参与。 - 安全边界:控制消息通道本身不做鉴权设计,
shutdown可远程终止实例、configuration_update可改写运行配置,因此该框架的暴露范围(监听地址、端口)应纳入运维安全考量,避免向不可信网络开放。
从 scripts/base/frameworks/control/main.zeek 的常量定义到 scripts/policy/frameworks/control/controller.zeek 的命令派发,再到 testing/btest/scripts/base/frameworks/control/ 的端到端测试,控制框架的每一条链路都能在仓库中找到可验证的落点。把它作为基础层,上层脚本可以自由挂接Control::configuration_update事件实现自定义的热更新逻辑,也可以扩展commands集合加入自己的远程命令,这正是 Zeek 把"远程控制"做成框架而非内置命令的原因。
- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
相关推荐
Zeek 控制框架(Control Framework)详解:基于 Broker 的远程运行时控制与信息采集
Zeek 控制框架(Control Framework)详解:基于 Broker 的远程运行时控制与信息采集 Control framework 是 Zeek
网络安全网络IDSZeek Control 框架实战:远程运行时控制的事件协议、配置项与命令行操作全解析
Zeek Control 框架实战:远程运行时控制的事件协议、配置项与命令行操作全解析 Zeek 的 Control 框架( base/frameworks/c
网络安全网络IDSrclone rc 命令详解:通过命令行调用运行中 rclone 的远程控制 API
rclone rc 命令详解:通过命令行调用运行中 rclone 的远程控制 API rclone rc 是 rclone 内置的远程控制(Remote Con
CLI数据同步对象存储
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考