- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
Zeek 的 Input 框架(input framework)是 Zeek 脚本层与外部数据源之间的桥梁,它允许用户把任意预先存储的数据读入 Zeek,既可以作为事件流逐条触发事件,也可以直接灌入 Zeek 表供查询使用。本文以 doc/scripts/base/frameworks/input/main.zeek.rst 中的框架接口为核心,结合 Zeek 仓库内的实际源码、默认脚本与测试用例,完整讲解 Input 框架的三大输入流类型、运行时选项、Reader 机制、异步处理模型与常见实战场景,读完即可在自有 Zeek 环境中落地数据导入方案。
Input 框架是什么
Input 框架提供了一条把"之前存储好的数据"读入 Zeek 的标准化通道,数据被读入后有两种归宿:事件流(event stream)与Zeek 表(table)。这一描述直接来自框架主脚本 scripts/base/frameworks/input/main.zeek 的开头注释:
The input framework provides a way to read previously stored data either as an event stream or into a Zeek table.
框架的脚本入口通过 scripts/base/frameworks/input/load.zeek 加载,它依次加载主脚本main以及 6 个内置 Reader 的接口脚本(ascii、raw、benchmark、binary、config、sqlite)。
从实现上看,框架本体位于 src/input,其中 src/input/Manager.cc 是核心管理器,负责创建/移除输入流、类型校验、线程化读取与数据回灌;每个 Reader 以插件形式存在(如 src/input/readers/ascii、src/input/readers/sqlite 等),主脚本通过 src/input/input.bif 把脚本层 API(Input::add_table等)绑定到底层 C++ 实现。
框架的三个输入流类型
Input 框架在脚本层提供三类描述记录(record),对应三种输入流:
| 类型 | 用途 |
|---|---|
Input::TableDescription | 表输入流:把数据写入 Zeek 表(或集合 set) |
Input::EventDescription | 事件输入流:把每一行数据转换成 Zeek 事件 |
Input::AnalysisDescription | 文件分析输入流:把输入数据直接转发给文件分析框架(file analysis framework) |
这三类记录的字段定义在 scripts/base/frameworks/input/main.zeek,并经由 src/input/Manager.cc 的CreateStream()统一做类型校验、Reader 初始化与 config 表传递。
全局运行时选项与可重定义配置
Input 框架提供了若干可全局&redef的选项,位于Input命名空间下,定义见 scripts/base/frameworks/input/main.zeek。
Runtime Options(运行时选项)
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Input::default_reader | Input::Reader | Input::READER_ASCII | 默认使用的输入 Reader |
Input::default_mode | Input::Mode | Input::MANUAL | 默认使用的读取模式 |
二者分别定义了"用哪个 Reader 读数据"和"以什么模式读数据",任何未显式指定$reader/$mode的输入流都会回落到这两个默认值。
Redefinable Options(可重定义选项)
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Input::separator | string | "\x09"(Tab) | 字段之间的分隔符,必须恰好一个字符 |
Input::set_separator | string | "," | 集合(set)元素之间的分隔符,必须恰好一个字符 |
Input::empty_field | string | "(empty)" | 空字段的占位字符串 |
Input::unset_field | string | "-" | 未设置的&optional字段的占位字符串 |
Input::accept_unsupported_types | bool | F | 是否接受包含不支持类型(目前为 file 与 function)的记录;为T时仅告警并继续,为F时直接中止 |
以Input::unset_field为例,默认值"-"正是 Zeek 日志系统中"字段未设置"的常规表示;用户数据里若用别的字符(如"NULL")表示空值,可&redef该选项。而Input::separator的默认 Tab 也说明框架默认对齐 Zeek 日志框架的输出格式——默认情况下面向的数据就是 Zeek 自己写出的 TSV 日志。
需要特别说明的是:这些全局选项是"默认值",文档明确注明"Individual readers can use a different value"(单个 Reader 可以使用不同值)。也就是说,ASCII Reader、Config Reader、SQLite Reader 各自拥有独立的同名可重定义选项,并且每个输入流还能通过$config表做流级覆盖(见下文)。
与默认选项相关的模式枚举
Input::Mode枚举定义了三种读取模式(scripts/base/frameworks/input/main.zeek):
| 枚举值 | 语义 |
|---|---|
Input::MANUAL | 读取完成后不自动重新读取(默认) |
Input::REREAD | 检测到文件变化时整文件重新读取 |
Input::STREAM | 每次有新数据追加时只从文件末尾读取新内容 |
Input::Reader枚举则列出内置的 6 个 Reader:READER_ASCII、READER_BENCHMARK、READER_BINARY、READER_CONFIG、READER_RAW、READER_SQLITE。
表输入流:把文件读入 Zeek 表
表输入流是最常用、也最能体现 Input 框架价值的用法:把外部数据(如封禁名单、资产清单)灌入 Zeek 表,供脚本实时查询。框架默认读取"Zeek 日志框架写出的格式"——即 Tab 分隔的 ASCII 文件,这也是 doc/frameworks/input.rst 中对默认 Reader 的描述。
定义索引与值记录类型
假设我们要导入一份 denylist(封禁名单),文件内容如下(注意字段必须 Tab 分隔,首行是#fields头):
#fields ip timestamp reason 192.168.17.1 1333252748 Malware host 192.168.27.2 1330235733 Botnet server 192.168.250.3 1333145108 Virus detected要在 Zeek 侧接收这份数据,需要定义两个 record 类型:一个描述"表键"的列,一个描述"表值"的列:
type Idx: record { ip: addr; }; type Val: record { timestamp: time; reason: string; };字段名必须与输入文件#fields行的列名一一对应(此处为ip、timestamp、reason)。列的顺序无关紧要,因为每个列按名称识别——这一识别逻辑在 src/input/Manager.cc 的UnrollRecordType()中体现,它按名字把输入字段展开为线程间传输的Field列表。
调用 Input::add_table
通过Input::add_table创建表输入流,其签名(定义见 scripts/base/frameworks/input/main.zeek):
Input::add_table([$source="denylist.file", $name="denylist", $idx=Idx, $val=Val, $destination=denylist]); Input::remove("denylist");$source:数据源标识,对 ASCII Reader 而言就是文件名;$name:输入流的唯一名称,后续用Input::remove/Input::force_update操纵该流时使用;$idx/$val:索引与值的 record 类型;$destination:接收数据的 Zeek 表(或 set)标识符。
由于读取是异步的(见下文),示例在zeek_init中先add_table再紧接着remove,二者按队列顺序执行:先完整读入,再移除流。要确认数据全部就绪,应监听Input::end_of_data事件:
event Input::end_of_data(name: string, source: string) { # now all data is in the table print denylist; }此后denylist就是一张普通 Zeek 表,可直接做成员测试:
if ( 192.168.18.12 in denylist ) # take action用 set 做成员测试
当数据本身只有"键"而无"值"语义时(比如只需要判断某 IP 是否命中),可以省略$val,把$destination设为 set:
type Idx: record { ip: addr; }; global denylist: set[addr] = set(); event zeek_init() { Input::add_table([$source="denylist.file", $name="denylist", $idx=Idx, $destination=denylist]); Input::remove("denylist"); }TableDescription记录中val: any &optional字段的注释明确写道:"If this is undefined, thendestinationmust be a set"(scripts/base/frameworks/input/main.zeek)。对应地,src/input/Manager.cc 的CreateTableStream()会做严格校验:目标为 set 时不能带$val,目标为 table 时又必须提供$val,否则直接报错。
&optional 字段与省略值
键记录和值记录都允许使用&optional字段,但省略列(通常用"-"表示)要格外小心:
- 键列不能省略:键记录会展开为表索引的值列表,索引所需的每个值都必须存在;
- 值列只有在声明了
&optional时才允许省略,否则框架会跳过该行并给出告警。
这是 Zeek 4.1 起的行为变化,此前版本会"不安全地"接受这类输入;从旧版本升级时,若输入数据存在省略字段,加载后的数据集可能与预期存在差异(详见 doc/frameworks/input.rst 中的说明)。
异步处理模型与线程化读取
Input 框架的读取是异步的:每一个输入流都会创建一个新的读取线程,该线程打开数据文件、把数据转换为内部格式并回传给 Zeek 主线程。因此数据不会立即出现在表中——根据数据源大小,可能需要几毫秒到几秒不等。
这个设计有两个直接后果:
- 如果 Zeek 运行时间极短(例如处理非常小的 pcap),可能在导入线程完成前就退出了;
- 对同一数据源的后续调用会被排队,直到前一个动作完成——这正是上面
add_table之后紧跟remove能正常工作的原因。
Input::end_of_data事件正是在数据源读取完毕后触发,收到该事件即代表表中数据已完整。底层实现中,src/input/Manager.cc 的ForceUpdate()同样走异步队列:它调用reader->Update()后立即返回,真正的更新在读取线程中完成,结束后再次触发end_of_data。
三种读取模式:MANUAL / REREAD / STREAM
对于不断变化的数据源(典型的如持续更新的封禁名单),Input 框架提供了两种自动刷新手段。
手动刷新:Input::force_update
只要输入流尚未被Input::remove,就可以随时调用:
Input::force_update("denylist");这会触发一次完整刷新:文件中变化的数据被更新、新增的数据被加入、文件中已删除的数据从表里移除。刷新完成后触发Input::end_of_data。force_update的签名为function(id: string) : bool,若指定的流不存在则返回F并报错(src/input/Manager.cc)。
自动重读:Input::REREAD
在add_table时指定$mode=Input::REREAD:
Input::add_table([$source="denylist.file", $name="denylist", $idx=Idx, $val=Val, $destination=denylist, $mode=Input::REREAD]);此后 Zeek 会持续检查输入文件是否发生变化;一旦发现变化,就整文件重读并同步表中数据。每次检测到变化且新数据全部读入后,触发一次end_of_data。
流式追加:Input::STREAM
指定$mode=Input::STREAM时,Zeek 假定输入文件是只追加(append-only)的文件,持续从文件末尾读取新增数据。它还支持文件轮转(rotation):当被跟踪文件的 inode 号变化时(说明文件被重命名/轮转),会关闭旧文件并重新打开新文件名对应的文件。
流式读取有几个关键行为:
- 新增行如果与表中已有行的索引相同,会覆盖表中的旧值;
- 由于数据持续追加,
Input::end_of_data事件在 STREAM 模式下永远不会触发。
心跳与变更检测机制
变更检测依赖周期性的"心跳"(heartbeat)事件,默认频率为每秒一次,由全局常量Threading::heartbeat_interval定义。Reader 通过比较文件 inode 或修改时间是否变化来判断文件是否被修改(见 doc/frameworks/input.rst 中的 tip 说明)。
表变更事件:精确追踪每一行变化
在 REREAD / STREAM 模式下,如果想知道"具体哪些行变了",可以注册一个变更事件:
event entry(description: Input::TableDescription, tpe: Input::Event, left: Idx, right: Val) { # do something here... print fmt("%s = %s", left, right); }然后在add_table中通过$ev绑定:
Input::add_table([$source="denylist.file", $name="denylist", $idx=Idx, $val=Val, $destination=denylist, $mode=Input::REREAD, $ev=entry]);事件参数语义:
description:原始add_table调用时传入的Input::TableDescription,可通过description$name访问流名;tpe:Input::Event枚举,标识变化的类型;left:发生变化的表项索引;right:与变化相关的值。
Input::Event枚举共三个取值(scripts/base/frameworks/input/main.zeek):
| 枚举值 | 触发条件 | left/right内容 |
|---|---|---|
Input::EVENT_NEW | 表中原本不存在的新行被加入 | 新项索引 / 新项值 |
Input::EVENT_CHANGED | 表中已有条目被修改 | 被修改项的索引 /变更前的值(因为事件触发时表已被更新,当前值需查表获取) |
Input::EVENT_REMOVED | 重读后表中不再存在的条目被移除 | 被移除项的索引 / 被移除的值 |
注意EVENT_CHANGED的right是旧值——事件触发时新值已经写入表,通过left查表即可对比新旧差异。事件必须按(description: Input::TableDescription, tpe: Input::Event, left: Idx, right: Val)的签名定义,底层CreateTableStream()会逐参数做严格类型校验(src/input/Manager.cc)。
谓词过滤:在导入阶段拦截或改写数据
谓词(predicate)函数让用户能在数据写入表之前决定是否接受、或直接改写数据。它的签名是:
pred: function(typ: Input::Event, left: any, right: any): bool返回T则执行变更,返回F则跳过。下面的例子拒绝"一个月以前生成的封禁条目",但接受所有变更与删除:
Input::add_table([$source="denylist.file", $name="denylist", $idx=Idx, $val=Val, $destination=denylist, $mode=Input::REREAD, $pred(tpe: Input::Event, left: Idx, right: Val) = { if ( tpe != Input::EVENT_NEW ) { return T; } return (current_time() - right$timestamp) < 30day; }]);关键时序语义:谓词在变更提交到表之前被调用。因此对于EVENT_CHANGED,left/right里是新值,而destination(表)里仍是旧值——谓词正好可以对比新旧再做决定。该特性在测试中有完整覆盖,参见 testing/btest/scripts/base/frameworks/input/predicate.zeek、predicatemodify.zeek 等用例。
事件输入流:把数据直接变成 Zeek 事件
与表输入流不同,事件输入流(Input::add_event)不把数据灌进表,而是逐行触发 Zeek 事件,让脚本按自己的方式处理。事件流与表流非常相似,大部分特性(如谓词)同样适用。
type Val: record { ip: addr; timestamp: time; reason: string; }; event denylistentry(description: Input::EventDescription, tpe: Input::Event, data: Val) { # do something here... print "data:", data; } event zeek_init() { Input::add_event([$source="denylist.file", $name="denylist", $fields=Val, $ev=denylistentry]); }事件流与表流有两个关键差异:
- 无需分开的索引/值声明:所有源数据类型都放在单个 record(
$fields)中; - 没有数据基线概念:框架无法拿当前数据与"表"做对比,因此
tpe参数目前恒为Input::EVENT_NEW。
Input::EventDescription记录还支持$want_record选项:为T(默认)时事件接收一个包含全部字段的 record 值;为F时每个字段作为独立参数传入。底层校验逻辑见 src/input/Manager.cc 的CreateEventStream()。
错误处理与 error_ev 事件
Input 框架通过两种方式报告数据摄取过程中的问题:
reporter 日志
问题消息会写入reporter.log,包含问题类型与出问题的文件:
#fields ts level message location 0.000000 Reporter::WARNING denylist.file/Input::READER_ASCII: Did not find requested field ip in input data file denylist.file. (empty)框架对同一文件的重复消息做了抑制,避免大文件中的错误引发消息洪泛。
error_ev 事件
Input::TableDescription与Input::EventDescription都带有可选的$error_ev成员,用于把与 reporter 日志相同的信息、按相同严重级别转成事件。其签名相当于:
error_ev: function(desc: TableDescription, message: string, level: Reporter::Level) &optional;(实际声明因 Zeek 类型系统的限制使用any类型。)当级别为 error 时,输入流会自动关闭。底层 src/input/Manager.cc 的CheckErrorEventTypes()会校验 error 事件恰好接收 3 个参数,且类型分别为描述记录、string、Reporter::Level。
ASCII Reader 的健壮性开关
InputAscii命名空间提供两个用于控制读取健壮性的可重定义选项(doc/scripts/base/frameworks/input/readers/ascii.zeek.rst):
| 选项 | 默认值 | 说明 |
|---|---|---|
InputAscii::fail_on_invalid_lines | F | 为F时跳过非法行并在reporter.log中告警;为T时视为致命错误,立即中止读取 |
InputAscii::fail_on_file_problem | F | 为T时对文件读取问题(权限、文件缺失等)失败;为F时忽略——对 REREAD 模式尤其有用,可在文件暂时不可访问时无缝恢复 |
InputAscii::path_prefix | "" | 为无路径或相对路径的 source 文件名加前缀;对绝对路径无效 |
InputAscii::separator | "\x09" | 字段分隔符,必须恰一个字符 |
InputAscii::set_separator | "," | set/vector 元素分隔符,必须恰一个字符 |
InputAscii::empty_field | "(empty)" | 空字段占位串 |
InputAscii::unset_field | "-" | 未设置&optional字段的占位串 |
这些选项既可通过&redef全局调整,也可通过输入流的$config表按流覆盖。fail_on_invalid_lines = T与fail_on_file_problem = T曾是 Bro 2.6 之前的默认行为,此后默认改为告警而非失败——迁移时需要注意。上述参数均有对应测试用例,如 testing/btest/scripts/base/frameworks/input/invalid-lines.zeek、invalid-lines-duplicate.zeek。
文件分析输入流:接入 file analysis 框架
第三种输入流Input::add_analysis创建文件分析输入流,数据读入后自动转发给文件分析框架(file analysis framework)。其描述记录Input::AnalysisDescription与另外两类不同:
reader字段默认值为Input::READER_BINARY(而非READER_ASCII),因为文件内容本质上是一个字节流;name字段同时用于唯一标识输入源,并作为fa_file的 source 字段——官方建议name与source取相同值;- 兼容的 Reader 必须能够接受"单个 string 类型的 filter"(即读取字节流)。
底层实现见 src/input/Manager.cc 的CreateAnalysisStream():它通过file_mgr->HashHandle(name)生成文件 ID,并给 Reader 初始化一个名为bytestream、类型为TYPE_STRING的单一字段。
Reader 选择机制与六种内置 Reader
Reader 的选择遵循以下规则:
Input::default_reader定义默认 Reader,即Input::READER_ASCII;- 调用
add_table/add_event时未指定$reader,则自动使用默认值; - 可在描述记录的
$reader成员中显式覆盖。
六种内置 Reader(实现位于 src/input/readers,接口文档位于 doc/scripts/base/frameworks/input/readers):
| Reader | 用途 |
|---|---|
READER_ASCII | 默认 Reader,理解 Zeek TSV 日志格式(Tab 分隔 +#fields头),默认值对齐 Zeek 日志输出 |
READER_BENCHMARK | 基准测试用,可生成任意规模、覆盖全部受支持类型的半随机数据,用于优化框架性能 |
READER_BINARY | 二进制数据,主要用于文件分析输入流摄取文件内容(该流默认 Reader) |
READER_CONFIG | 配置文件读取,InputConfig::new_value事件在配置项新增/变化时触发 |
READER_RAW | 按行读取文本(行分隔符可配,默认换行),逐行以 string 返回,适合读配置文件等,通常只在事件模式下有意义 |
READER_SQLITE | 读取 SQLite 数据库,通过$config表的"query"键指定 SQL 查询 |
Config Reader
Config Reader 的接口文档见 doc/scripts/base/frameworks/input/readers/config.zeek.rst。其InputConfig::new_value事件在配置项新增或变化时触发:
event InputConfig::new_value(name: string, source: string, id: string, value: any)注意该事件不追踪变化原因(新增/修改),也不追踪删除;如果需要这类语义,官方建议把 Config Reader 与表 Reader 组合使用。其可重定义选项包括InputConfig::empty_field(默认"",即空输入字段产生空 set)、InputConfig::fail_on_file_problem(默认F)、InputConfig::set_separator(默认",")。
SQLite Reader
SQLite Reader 让 Zeek 直接查询 SQLite 数据库(doc/scripts/base/frameworks/input/readers/sqlite.zeek.rst)。使用时必须在$config表中设置"query"键来指定返回所需数据的 SQL 查询。可重定义选项包括InputSQLite::empty_field(默认"(empty)")、InputSQLite::set_separator(默认",")、InputSQLite::unset_field(默认"-")。更完整的使用介绍见 doc/frameworks/logging-input-sqlite.rst。
Raw Reader 与 JSON Lines 解析
ASCII Reader 目前不支持 JSON,但可以组合Raw Reader + 内置from_json函数来读取 JSON Lines 格式(自 Zeek 6.0 起)。示例见 doc/frameworks/input/input_json_1.zeek 与配套数据 doc/frameworks/input/denylist.jsonl:
event Denylist::json_line(description: Input::EventDescription, tpe: Input::Event, l: string) { local parse_result = from_json(l, Entry); if ( ! parse_result$valid ) return; local entry = parse_result$v as Entry; staged_denies[entry$ip] = entry; } event zeek_init() { Input::add_event([ $source="denylist.jsonl", $name="denylist", $reader=Input::READER_RAW, $mode=Input::REREAD, $fields=JsonLine, $ev=Denylist::json_line, $want_record=F, ]); }该示例使用两张状态表(staging / active),在Input::end_of_data时切换,从而支持对已读数据的删除更新。
底层实现要点:从脚本 API 到 C++ 管理器
Input 框架的脚本层 API 最终都绑定到 C++ 管理器。调用链如下:
- scripts/base/frameworks/input/main.zeek 中的
add_table/add_event/add_analysis/remove/force_update是薄封装,分别调用__create_table_stream、__create_event_stream、__create_analysis_stream、__remove_stream、__force_update; - 这些内部函数定义在 src/input/input.bif,各自转发给
zeek::input_mgr的对应方法; - src/input/Manager.cc 中:
CreateStream()负责通用校验与 Reader 前端创建;CreateEventStream()/CreateTableStream()/CreateAnalysisStream()分别处理三类流;RemoveStream()走"先标记移除、Reader 停止后异步完成删除"的流程;ForceUpdate()异步触发reader->Update()。
从源码结构可以推断几个关键设计:每个流持有独立的ReaderFrontend(前端运行在主线程,后端读取线程见 src/input/ReaderBackend.cc 与 src/input/ReaderFrontend.cc);表流内部用PDict<InputHash>记录每一行的索引/值哈希(src/input/Manager.cc),用于在 REREAD 时精确计算新增、变更与删除;Reader 本身以插件形式注册,Manager::CreateBackend()通过插件工厂实例化(src/input/Manager.cc)。
UnrollRecordType()(src/input/Manager.cc)负责把 Zeek record 类型"展开"为平面字段列表,同时校验每个字段的类型兼容性:IsCompatibleType()支持 bool、int、count、port、subnet、addr、double、time、interval、enum、string、pattern 等原子类型,以及 record、set、vector 等复合类型;file与function类型默认不被接受,除非accept_unsupported_types开启且字段带&optional属性。
测试用例:验证与进阶学习
Input 框架在 testing/btest/scripts/base/frameworks/input 下有超过 60 个回归测试用例,覆盖了几乎所有特性,非常适合作为进阶学习与验证材料:
- 基础导入:basic.zeek、set.zeek、twotables.zeek;
- 三种模式:reread.zeek、stream.zeek;
- 谓词:predicate.zeek、predicatemodify.zeek、predicate-stream.zeek;
- 事件流:event.zeek、tableevent.zeek、subrecord-event.zeek;
- 错误处理:errors.zeek、invalid-lines.zeek、unsupported_types.zeek;
- Raw Reader 与 JSON:raw 目录、from_json.zeek;
- SQLite:sqlite 目录(basic、error、port、types);
- 路径前缀:path-prefix 目录(absolute-prefix、relative-prefix 等)。
注意事项与最佳实践
- 文件锁与原子替换:ASCII Reader 没有文件锁概念(包括 UNIX advisory lock)。大文件可能被读到"仍在写入中"的状态,框架虽能通过 reporter 日志稳健处理错误,但最稳妥的做法是:先在侧面生成新文件,再原子重命名为框架监控的文件名。
- 短运行场景:异步导入意味着 Zeek 可能在数据就绪前退出,务必用
Input::end_of_data作为"数据可用"信号。 - 集群部署:Input 框架本身对 Zeek 集群架构无感知,集群任一节点均可使用上述全部机制;Config 框架与 Intel 框架正是基于 Input 框架,在 manager 节点上摄取数据并通过事件分发到集群(见 doc/frameworks/input.rst 的 note 说明)。
- 流级配置覆盖:全局
&redef选项只是默认值,优先通过每个输入流的$config表传递 Reader 特有参数(如 SQLite 的query),实现单流定制。
- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
相关推荐
Zeek 输入框架(Input Framework)完全指南:从 `__load__.zeek` 模块加载到六大 Reader 的实战应用
Zeek 输入框架(Input Framework)完全指南:从 __load__.zeek 模块加载到六大 Reader 的实战应用 本文以 Zeek 仓库中
网络安全网络IDSZeek Raw Reader(Input::READER_RAW)输入框架指南:读取文件与命令输出的完整实践
Zeek Raw Reader(Input::READER_RAW)输入框架指南:读取文件与命令输出的完整实践 导读 本文聚焦 Zeek 输入框架(Input
网络安全网络IDSZeek Input Framework 实战指南:从 TSV 表格导入到 SQLite 数据库查询
Zeek Input Framework 实战指南:从 TSV 表格导入到 SQLite 数据库查询 Zeek(原 Bro)提供了一套灵活强大的输入框架(Inp
网络安全网络IDS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考