☰
Zeek NetControl 流分流(Shunt)功能详解:shunt_flow 接口、netcontrol_shunt 日志与实战
2026/10/8 14:03:06 网站建设 项目流程
  • 网络安全
  • 网络
  • IDS

【免费下载链接】zeek

Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.

项目地址:https://gitcode.com/gh_mirrors/ze/zeek
点击查看免费下载

本文基于 Zeek 仓库中 doc/scripts/base/frameworks/netcontrol/shunt.zeek.rst 及 shunt.zeek 源码,深入讲解 NetControl 框架的 shunt(分流)功能。shunt 用于告诉网络设备"停止把某个单向流的报文继续转发给 Zeek",从而让 Zeek 在识别出良性流量后节省分析资源。读完本文,你将掌握NetControl::shunt_flow的完整签名与参数语义、NetControl::ShuntInfo日志记录结构、netcontrol_shunt日志流的字段含义,并能写出可运行的 shunt 触发脚本,理解其底层规则机制与测试验证方式。

NetControl 框架中的 Shunt 定位

Zeek 的 NetControl 框架(见 doc/frameworks/netcontrol.rst)是一个基于插件的流量控制框架:它既能控制发往 Zeek 自身的"监控路径",也能(在具备转发路径访问权限时)控制网络设备的"转发路径"。默认情况下框架放行所有流量,脚本可以添加规则对特定实体施加限制。

Shunt 正是框架在监控路径上的典型应用。框架类型定义(types.zeek)将规则目标分成两类:

  • NetControl::FORWARD:把规则主动应用到网络转发路径上,影响所有网络流量;
  • NetControl::MONITOR:把规则被动应用到发往 Zeek 的监控流量上。

shunt_flow创建的是target=MONITOR的规则,含义即"Zeek 告诉网络硬件,它不想再看到已被判定为良性的流量"。这与 drop 系列函数(target=FORWARD,阻断转发)形成鲜明对照:shunt 不改变网络中报文的去向,只是让 Zeek 不再接收它们。

官方框架文档对shunt_flow的定位是:

Calling this function causes NetControl to stop forwarding a uni-directional flow of packets to Zeek. This allows Zeek to conserve resources by shunting flows that have been identified as being benign.

shunt_flow:接口签名与参数语义

shunt_flow在 shunt.zeek 中声明,是 NetControl 高层 API 中专门面向单向流的一个入口:

global shunt_flow: function(f: flow_id, t: interval, location: string &default="") : string;
参数类型说明
fflow_id要被 shunt 的单向流,包含源/目的 IP 与端口
tintervalshunt 保留的时间;0表示无限期保留
locationstring(可选,默认"")描述 shunt 触发位置的字符串

返回值:成功时返回插入规则的 ID 字符串;失败时返回空字符串("")。注意返回值语义与add_rule保持一致:所谓"成功"指有插件接受了这条规则,并不保证硬件层面已真正生效,因为硬件下发是异步的(见 main.zeek)。

参数细节

  • flow_id 与单向流:flow_id是 Zeek 内建类型,由src_h/src_p与dst_h/dst_p(外加可选的 MAC 字段)构成。shunt 针对的是单向流,因此调用方应传入对应方向(如 orig 方向或 resp 方向)的四元组。
  • expire 语义:t即规则过期时间。传入0sec表示不过期,规则会一直保留直到被显式remove_rule;传入具体时长则到期后规则超时,NetControl 会通过rule_timeout事件通知插件进行清理(参见 main.zeek)。
  • location 的审计价值:该字段会被写入日志,用于追溯"是哪段脚本/哪个策略触发了这次 shunt",在多脚本协同的部署中尤其重要。

源码实现:shunt_flow 内部发生了什么

shunt_flow的实现位于 shunt.zeek,核心流程分为三步:构造 Flow 实体 → 构造 DROP/MONITOR 规则 → 调用add_rule并写日志。

function shunt_flow(f: flow_id, t: interval, location: string &default="") : string { local flow = NetControl::Flow( $src_h=f$src_h as subnet, $src_p=f$src_p, $dst_h=f$dst_h as subnet, $dst_p=f$dst_p ); local e = Entity($ty=FLOW, $flow=flow); local r = Rule($ty=DROP, $target=MONITOR, $entity=e, $expire=t, $location=location); local id = add_rule(r); # Error should already be logged if ( id == "" ) return id; local log = ShuntInfo($ts=network_time(), $rule_id=id, $f=f, $expire=t); if ( location != "" ) log$location=location; Log::write(SHUNT, log); return id; }

关键实现细节

  1. 地址升格为子网:f$src_h as subnet与f$dst_h as subnet把 IP 地址转成/32(或/128)子网。这是因为 NetControl 的Flow类型(types.zeek)中src_h/dst_h声明为subnet,未设置的字段会被解释为通配符。也就是说,你可以手动构造带有通配符的NetControl::Flow来实现对整段网段的 shunt。
  2. 实体类型:Entity($ty=FLOW, ...)对应EntityType枚举中的FLOW——"涉及单向流活动的实体"(types.zeek)。
  3. 规则类型:Rule($ty=DROP, $target=MONITOR, ...)。DROP规则类型表示"停止转发匹配实体的所有报文"(types.zeek),配合MONITOR目标即实现"不再把该流送给 Zeek"的语义。
  4. 失败即短路:add_rule返回空串表示没有插件接受规则(错误已由框架写入 netcontrol 日志),此时直接返回,不再写 shunt 日志。
  5. 日志只记成功:只有规则被接受后才构造ShuntInfo并调用Log::write(SHUNT, log),保证netcontrol_shunt.log中的每行都对应一次成功的 shunt。

规则生命周期与优先级

规则加入框架后由 main.zeek 全生命周期跟踪:rule_new在add_rule时触发,rule_added/rule_exists/rule_timeout/rule_error/rule_removed分别汇报各阶段状态,rule_destroyed表示规则彻底退出跟踪。多条规则同时命中同一实体时,按priority排序取第一条(priority越大优先级越高,默认值由NetControl::default_priority决定,见 types.zeek)。若希望 shunt 规则能压过已有的低优先级 drop 规则,可以手动构造更高优先级的规则;高层shunt_flow本身使用默认优先级。

netcontrol_shunt 日志:ShuntInfo 记录结构

每次成功的 shunt 都会写入netcontrol_shunt日志流。日志列由ShuntInforecord 定义(shunt.zeek),各字段均带&log属性:

字段类型说明
tstime记录活动发生的时间
rule_idstring规则 ID;在一次 Zeek 运行内唯一
fflow_id被 shunt 的流的 ID(四元组)
expireintervalshunt 的过期时长
locationstring(可选)触发底层动作的位置描述

日志流在zeek_init(优先级 5)中注册(shunt.zeek):

event zeek_init() &priority=5 { Log::create_stream(NetControl::SHUNT, Log::Stream($columns=ShuntInfo, $ev=log_netcontrol_shunt, $path="netcontrol_shunt", $policy=log_policy_shunt)); }
  • $path="netcontrol_shunt"决定日志文件名,即netcontrol_shunt.log。
  • $ev=log_netcontrol_shunt绑定事件,任何脚本都可以订阅它来在记录落地前访问ShuntInforecord。
  • $policy=log_policy_shunt绑定策略钩子NetControl::log_policy_shunt(Log::PolicyHook),可在写日志前增删改字段或过滤记录。

可订阅的事件与钩子

  • event NetControl::log_netcontrol_shunt(rec: NetControl::ShuntInfo):日志写往框架前触发,用于统一加工或转发 shunt 记录。
  • hook NetControl::log_policy_shunt(rec: Log::PolicyHook, rec: any)(类型为Log::PolicyHook):标准的日志策略钩子,可用break阻止该条记录落盘,或直接改写rec字段。

下面的示例演示如何利用策略钩子为 shunt 日志附加过滤(例如只保留 location 非空的记录):

hook NetControl::log_policy_shunt(rec: NetControl::ShuntInfo) { if ( ! rec?$location ) break; # 丢弃没有 location 信息的 shunt 记录 }

实战:在脚本中触发一条 shunt

要实际触发 shunt,至少需要一个已激活的 NetControl 后端(插件)。最简单的后端是 debug 插件(plugins/debug.zeek),它把所有动作打印到标准输出。下面是一个完整可运行的示例,参照官方集成测试 testing/btest/scripts/base/frameworks/netcontrol/basic.zeek 的结构编写:

@load base/frameworks/netcontrol event NetControl::init() { local netcontrol_debug = NetControl::create_debug(T); NetControl::activate(netcontrol_debug, 0); } event NetControl::init_done() &priority=-5 { # 把 192.168.17.1:32/tcp -> 192.168.17.2:32/tcp 的单向流 shunt 30 秒 NetControl::shunt_flow( [$src_h=192.168.17.1, $src_p=32/tcp, $dst_h=192.168.17.2, $dst_p=32/tcp], 30sec, "example-script" ); }

运行zeek basic.zeek后,debug 插件会打印规则内容;同时生成netcontrol_shunt.log。官方测试的基线文件 testing/btest/Baseline/scripts.base.frameworks.netcontrol.basic/netcontrol_shunt.log 展示了真实输出结构:

#fields ts rule_id f.src_h f.src_p f.dst_h f.dst_p expire location #types time string addr port addr port interval string XXXXXXXXXX.XXXXXX 2 192.168.17.1 32 192.168.17.2 32 30.000000 -

可见f字段被展开为f.src_h/f.src_p/f.dst_h/f.dst_p四列;本例未传location,因此取值为-(unset)。

测试验证:shunt 功能如何被自动化检验

Zeek 仓库用 btest 对 shunt 功能做了端到端验证。测试 testing/btest/scripts/base/frameworks/netcontrol/basic.zeek 在NetControl::init_done中依次调用shunt_flow、drop_address、whitelist_address、redirect_flow、quarantine_host等高层函数,并对比netcontrol.log、netcontrol_shunt.log、netcontrol_drop.log与标准输出。其测试头注释:

# @TEST-EXEC: zeek -b %INPUT # @TEST-EXEC: btest-diff netcontrol.log # @TEST-EXEC: btest-diff netcontrol_shunt.log
  • 测试先通过NetControl::create_debug(T)激活 debug 后端(T表示接受所有操作);
  • 再调用shunt_flow([$src_h=192.168.17.1, $src_p=32/tcp, $dst_h=192.168.17.2, $dst_p=32/tcp], 30sec);
  • 基线文件确认netcontrol_shunt.log中只有一行成功记录,且rule_id为2(与同一脚本中其他规则按插入顺序递增的 ID 对应)。

这组测试同时验证了:shunt 只影响被指定流、规则被 debug 后端接受、日志字段(尤其是f的四元组展开)序列化正确。

使用注意事项与边界

  1. 必须有活动后端:add_rule只有在至少一个插件接受规则时才返回非空 ID。若NetControl::init中未激活任何后端,shunt_flow会静默返回""(错误信息写入 netcontrol 日志),netcontrol_shunt.log中不会有对应记录。
  2. 成功≠已生效:与框架整体语义一致,返回值只代表"有插件接管了规则",硬件层面的实际下发是异步的;如需精确状态,应订阅rule_added/rule_error等事件(main.zeek)。
  3. 单向流语义:flow_id是单向的,若要屏蔽双向流量需要分别对两个方向调用shunt_flow(或改用面向连接的drop_connection)。
  4. 资源回收:t传0表示永久 shunt,需要后续显式调用NetControl::remove_rule(id)才能撤销,注意避免规则表无限增长;固定时长则到期自动清理。
  5. location 是可选的审计线索:在大型策略集中建议始终传入,便于从日志反查触发点。

延伸阅读

  • 框架整体架构与高层 API 总览:doc/frameworks/netcontrol.rst(其中shunt_flow被列为与drop_address、redirect_flow、quarantine_host并列的高层函数)
  • 同系列实现对比:drop.zeek(DROP/FORWARD语义)、main.zeek(规则生命周期与add_rule低层 API)
  • 核心类型定义:types.zeek(Flow、Entity、Rule、TargetType、RuleType)
  • 后端插件示例:plugins/debug.zeek
  • 官方集成测试:testing/btest/scripts/base/frameworks/netcontrol/basic.zeek 及其 netcontrol_shunt.log 基线
  • 网络安全
  • 网络
  • IDS

【免费下载链接】zeek

Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.

项目地址:https://gitcode.com/gh_mirrors/ze/zeek
点击查看免费下载

相关推荐

上一篇:Kritis源码解析:Admission Controller如何实现部署时策略检查
下一篇:如何免费快速搭建个人专属的微信公众号RSS阅读器:WeWe RSS终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询