- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
导读
本文以 Zeek 仓库中 doc/scripts/base/bif/pcap.bif.zeek.rst 为骨架,系统讲解Pcap命名空间下五个与抓包过滤相关的内置函数:Pcap::precompile_pcap_filter、Pcap::install_pcap_filter、Pcap::error、Pcap::get_filter_state、Pcap::get_filter_state_string,以及枚举设备接口的Pcap::findalldevs。读完本文,你将掌握如何在 Zeek 脚本中动态编译、安装、查询 BPF 过滤器,如何读懂过滤器的编译状态与错误信息,并理解这些函数在 Zeek 底层iosource与packet-filter框架中的真实调用链。所有结论均以仓库内 src/iosource/pcap/pcap.bif、scripts/base/frameworks/packet-filter/main.zeek 与测试用例为准。
一、Pcap 模块概览:从 BIF 到抓包内核
在 Zeek 中,Pcap命名空间封装了与 libpcap 交互的内置函数,它们通过.bif(Built-In Function)机制生成 Zeek 脚本可直接调用的函数。对应的 BIF 源文件位于 src/iosource/pcap/pcap.bif,文档中给出的函数签名与行为均由该文件中的%%{ ... }%%C++ 代码实现。
该模块同时声明了抓包相关的常量,位于 scripts/base/init-bare.zeek:
| 常量 | 类型 | 默认值 | 含义 |
|---|---|---|---|
snaplen | count | — | 抓包快照长度 |
bufsize | count | — | 在线抓包缓冲区大小 |
bufsize_offline_bytes | count | — | 离线(读 pcap 文件)缓冲区字节数 |
non_fd_timeout | interval | 20usec | 非文件描述符型包源的轮询超时 |
这些常量说明Pcap模块不仅提供过滤函数,也承载着 libpcap 底层抓包参数的配置入口。
Pcap模块涉及的两个核心数据类型同样定义在 scripts/base/init-bare.zeek 中:
PcapFilterID(init-bare.zeek#L1533-L1535):枚举类型,初始只有None,用于标识不同的 BPF 过滤器;Pcap::filter_state(init-bare.zeek#L5975-L5980):枚举{ ok, fatal, warning },描述过滤器编译/初始化的状态;Pcap::Interface/Pcap::Interfaces(init-bare.zeek#L5955-L5973):描述抓包接口的记录类型与接口集合。
二、五个核心函数一览
原文档 pcap.bif.zeek.rst 以 Summary 表格的形式列出全部函数,整理如下:
| 函数 | 签名 | 作用 |
|---|---|---|
Pcap::precompile_pcap_filter | (id: PcapFilterID, s: string): bool | 预编译 PCAP 过滤器并绑定到给定标识符 |
Pcap::install_pcap_filter | (id: PcapFilterID): bool | 安装已预编译的过滤器 |
Pcap::error | (): string | 返回最后一次 PCAP 错误的字符串表示 |
Pcap::get_filter_state | (id: PcapFilterID): Pcap::filter_state | 返回过滤器初始化状态 |
Pcap::get_filter_state_string | (id: PcapFilterID): string | 返回过滤器初始化产生的错误信息字符串 |
Pcap::findalldevs | (): Pcap::Interfaces | 枚举本机所有可用的抓包设备 |
其中前五个构成"编译 → 安装 → 查询 → 排错"的完整动态过滤工作流;findalldevs则用于设备发现。
三、预编译过滤器:Pcap::precompile_pcap_filter
3.1 文档定义的接口
function Pcap::precompile_pcap_filter(id: PcapFilterID, s: string): bool- 参数
id:用于后续引用过滤器s的 PCAP 标识符; - 参数
s:PCAP 过滤表达式,合法语法参见man tcpdump; - 返回值:
s合法且预编译成功时返回T,否则返回F。
3.2 源码实现与限制
src/iosource/pcap/pcap.bif#L36-L60 中的实现揭示了两个关键细节:
- ID 空间上限为 100:
id->AsEnum() >= 100时直接报错并返回false。原因是底层用 vector 存储过滤器以支持快速查找,限制 ID 空间防止其无限增长。错误信息为"PCAP filter ids must remain below 100 (is N)"。 - 编译状态联动:实现通过
zeek::iosource_mgr->GetPktSrc()获取当前活动的包源(PktSrc),调用其PrecompileFilter(id, s)(虚方法,默认实现见 src/iosource/PktSrc.h#L188),随后取得对应的BPF_Program并检查其状态:只有编译成功且过滤器状态为FilterState::OK时,函数才返回T。
BPF_Program是对 libpcapstruct bpf_program的封装,定义于 src/iosource/BPF_Program.h,其Compile()方法内部对应pcap_compile()。对 libpcap 源而言,编译发生在 src/iosource/pcap/Source.cc#L266-L279 的PcapSource::CompileFilter()中,失败时错误信息形如cannot compile BPF filter "..." : <详情>。
3.3 何时使用
预编译的目的是把语法检查与真正的安装分离:脚本可以先在任何时刻(例如zeek_init)验证过滤器表达式是否合法,再在合适的时机(如某个事件触发时)将其安装生效,避免在流量处理的关键路径上执行昂贵的编译。
四、安装过滤器:Pcap::install_pcap_filter
4.1 文档定义的接口
function Pcap::install_pcap_filter(id: PcapFilterID): bool- 参数
id:已通过Pcap::precompile_pcap_filter预编译过的过滤器标识符; - 返回值:与
id关联的过滤器安装成功时返回T。
4.2 源码实现
src/iosource/pcap/pcap.bif#L80-L89 中,安装动作最终落到包源的SetFilter(id)(纯虚方法,见 src/iosource/PktSrc.h#L202):
zeek::iosource::PktSrc* ps = zeek::iosource_mgr->GetPktSrc(); if ( ps && ! ps->SetFilter(id->AsInt()) ) success = false;不同的包源实现各不相同:
- libpcap 源
PcapSource:在 src/iosource/pcap/Source.cc#L282 实现,将预编译的 BPF 程序绑定到 pcap 句柄; - AF_Packet 源
AF_PacketSource:在 src/iosource/af_packet/AF_Packet.cc#L318-L322 实现PrecompileFilter与SetFilter; - pcapng 源:
SetFilter直接返回true(src/iosource/pcapng/Source.h#L36),即对 pcapng 输入不做过滤。
这说明Pcap模块的过滤器机制是包源无关的抽象,同一套脚本接口可驱动不同底层抓包引擎。
4.3 典型用法:先预编译,再安装
仓库测试 testing/btest/core/pcap/dynamic-filter.zeek 展示了标准的两阶段用法:
redef enum PcapFilterID += { A, B }; event zeek_init() { if ( ! Pcap::precompile_pcap_filter(A, "port 80") ) print "error 1"; if ( ! Pcap::precompile_pcap_filter(B, "port 53") ) print "error 2"; } event new_packet(c: connection, p: pkt_hdr) { ++cnt; if ( cnt == 1 ) if ( ! Pcap::install_pcap_filter(A) ) print "error 3"; if ( cnt == 2 ) if ( ! Pcap::install_pcap_filter(B) ) print "error 4"; }该测试使用wikipedia.pcap离线流量:zeek_init阶段预编译port 80与port 53两个过滤器,然后在new_packet事件中先后安装,验证"先编译后安装"的完整链路。
五、读取过滤状态:get_filter_state与get_filter_state_string
5.1Pcap::get_filter_state
function Pcap::get_filter_state(id: PcapFilterID): Pcap::filter_state返回过滤器初始化状态;若当前没有活动包源,或过滤器 ID 不存在,则返回ok。
实现见 src/iosource/pcap/pcap.bif#L128-L139:通过ps->GetBPFFilter(id)获取BPF_Program后,将其GetState()映射为Pcap::filter_state枚举值返回。FilterState的 C++ 定义在 src/iosource/BPF_Program.h#L16-L20:
enum class FilterState : uint8_t { OK, FATAL, // results in Reporter::Error WARNING // results in Reporter::Warning };与 Zeek 侧的枚举{ ok, fatal, warning }(init-bare.zeek#L5976-L5980)一一对应:fatal表示会阻止 Zeek 继续运行的问题,warning表示非致命、仅需记录的告警。
5.2Pcap::get_filter_state_string
function Pcap::get_filter_state_string(id: PcapFilterID): string返回过滤器初始化过程中报告的警告/错误信息:
- 有错误信息时返回对应字符串;
- 未遇到任何问题时返回空字符串;
- 若没有活动包源或过滤器 ID 不存在,返回
"<unknown>"。
实现(src/iosource/pcap/pcap.bif#L152-L162)直接返回BPF_Program::GetStateMessage()(src/iosource/BPF_Program.h#L76),这是 libpcap 编译过程中产生的原始错误文本。
5.3 在 PacketFilter 框架中的组合使用
这两个函数被 scripts/base/frameworks/packet-filter/main.zeek 中的install()函数组合使用,构成完整的失败处理流程(main.zeek#L300-L326):
if ( ! Pcap::precompile_pcap_filter(DefaultPcapFilter, tmp_filter) ) { local state = Pcap::get_filter_state(DefaultPcapFilter); if ( state == Pcap::fatal ) { NOTICE(Notice::Info($note=Compile_Failure, $msg=fmt("Compiling packet filter failed"), $sub=tmp_filter)); error_string = fmt("Bad pcap filter '%s': %s", tmp_filter, Pcap::get_filter_state_string(DefaultPcapFilter)); if ( network_time() == 0.0 ) Reporter::fatal(error_string); else Reporter::warning(error_string); } else if ( state == Pcap::warning ) Reporter::warning(fmt("Warning while compiling pcap filter '%s': %s", tmp_filter, Pcap::get_filter_state_string(DefaultPcapFilter))); }安装失败时同样使用状态字符串作为日志与 Notice 的原因(main.zeek#L347-L356)。由此可见,get_filter_state决定错误等级(fatal/warning),get_filter_state_string提供可读的排错细节,二者配合支撑了Compile_Failure、Install_Failure、Too_Long_To_Compile_Filter三类 Notice(main.zeek#L21-L30)。
六、获取错误信息:Pcap::error
function Pcap::error(): string返回最后一次 PCAP 函数失败的描述性错误信息;若没有活动包源或没有错误,则返回"no error"。实现见 src/iosource/pcap/pcap.bif#L105-L116,直接返回包源的ErrorMsg()。该函数通常与下面第七节的地址/子网过滤函数配合使用,作为失败诊断的补充。
七、相关联的包过滤函数族
Pcap模块的函数在文档中多次以zeek:see::交叉引用一组全局过滤函数,它们定义在 src/zeek.bif 中,用于按概率丢弃特定方向的报文:
| 函数 | 签名 | 作用 |
|---|---|---|
install_src_addr_filter | (ip: addr, tcp_flags: count, prob: double): bool | 安装按源地址丢弃报文的过滤器 |
install_src_net_filter | (snet: subnet, tcp_flags: count, prob: double): bool | 安装按源子网丢弃报文的过滤器 |
uninstall_src_addr_filter | (ip: addr): bool | 移除源地址过滤器 |
uninstall_src_net_filter | (snet: subnet): bool | 移除源子网过滤器 |
install_dst_addr_filter | (ip: addr, tcp_flags: count, prob: double): bool | 安装按目的地址丢弃报文的过滤器 |
install_dst_net_filter | (snet: subnet, tcp_flags: count, prob: double): bool | 安装按目的子网丢弃报文的过滤器 |
uninstall_dst_addr_filter | (ip: addr): bool | 移除目的地址过滤器 |
uninstall_dst_net_filter | (snet: subnet): bool | 移除目的子网过滤器 |
以install_src_addr_filter为例(src/zeek.bif#L5333-L5337),其实现委托给packet_mgr->GetPacketFilter()->AddSrc(...),tcp_flags表示"若这些 TCP 标志位均未设置,则以prob概率丢弃来自该地址/子网的报文",prob取值区间为[0.0, 1.0]。这类过滤器用于丢包采样与缓解而非 BPF 语法过滤,与Pcap::precompile_pcap_filter的机制互补。
八、枚举抓包设备:Pcap::findalldevs
function Pcap::findalldevs(): Pcap::Interfaces返回本机全部可用抓包接口的集合,类型为Pcap::Interfaces,即set[Pcap::Interface]。实现(src/iosource/pcap/pcap.bif#L164-L216)直接调用 libpcap 的pcap_findalldevs(),失败时通过emit_builtin_error报告错误并返回空集合。
返回的Pcap::Interface记录(scripts/base/init-bare.zeek#L5955-L5971)字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 接口/设备名 |
description | string &optional | 设备的人类可读描述 |
addrs | set[addr] | 设备关联的网络地址 |
is_loopback | bool | 是否为回环接口(如127.0.0.1、[::1]) |
is_up | bool &optional | 设备是否处于 up 状态(libpcap ≥ 1.6.1 可用) |
is_running | bool &optional | 设备是否处于运行状态(libpcap ≥ 1.6.1 可用) |
值得注意的实现细节:is_up与is_running字段被#ifdef PCAP_IF_UP保护(pcap.bif#L205-L209),因为这两个标志直到 libpcap 1.6.1 才出现——这体现了 Zeek 对旧版 libpcap 的兼容性处理。IPv4 与 IPv6 地址会分别从sockaddr_in/sockaddr_in6转换为 Zeek 的addr类型后以set[addr]返回。
九、深入实践:packet-filter框架中的动态过滤机制
9.1 框架如何扩展PcapFilterID
PcapFilterID初始只有None(init-bare.zeek#L1533-L1535),而 packet-filter/main.zeek#L150-L153 通过redef enum PcapFilterID += { DefaultPcapFilter, FilterTester }增加两个内置 ID:
DefaultPcapFilter:当前生效的抓包过滤器;FilterTester:用于验证过滤器表达式合法性的临时 ID。
test_filter()函数(main.zeek#L155-L164)演示了FilterTester的用途:先用Pcap::precompile_pcap_filter(FilterTester, filter)验证表达式,失败即返回F。Zeek 启动时用它对capture_filters与restrict_filters中的每个过滤器做预验证,非法表达式直接触发Reporter::fatal(main.zeek#L180-L192)。
9.2 过滤器的组装优先级
框架的build()函数(main.zeek#L242-L285)揭示了 Zeek 中各类过滤器的最终组合逻辑:
- 命令行过滤器优先:若通过
-f等命令行参数指定了cmd_line_bpf_filter,直接采用,覆盖脚本定义的所有过滤器; - 否则按
capture_filters表以or合并捕获过滤器;未定义时使用default_capture_filter(默认"ip or not ip",即捕获所有流量); - 若启用
enable_auto_protocol_capture_filters,再与Analyzer::get_bpf()按协议自动生成的过滤器做or合并; - 所有
restrict_filters与动态dynamic_restrict_filters(即exclude添加的"排除"过滤器,自动包上not (...))以and合并为限制过滤器; - 最后将捕获过滤器与限制过滤器以
and组合,并叠加unrestricted_filter(or)与restricted_filter(and)两个全局常量(默认均为空串)。
9.3 动态过滤的完整生命周期
脚本可通过exclude(filter_id, filter)、exclude_for(filter_id, filter, span)、remove_exclude(filter_id)动态增删排除过滤器(main.zeek#L206-L240)。每次变更都会触发install()重新构建并安装过滤器:
install()先比较新过滤器与current_filter,未变化则直接返回(避免无谓重装,见 main.zeek#L294-L296);- 接着调用
Pcap::precompile_pcap_filter(DefaultPcapFilter, tmp_filter); - 预编译通过后,调用
Pcap::install_pcap_filter(DefaultPcapFilter)真正生效; - 若编译耗时超过
max_filter_compile_time(默认100msec),触发Too_Long_To_Compile_FilterNotice(main.zeek#L327-L330); - 每次安装尝试都会写入
packet_filter日志流(Info记录含ts、node、filter、init、success、failure_reason等字段,见 main.zeek#L34-L54),便于审计与排障。
此外,filter_change_tracking事件每 5 分钟检查一次filter_changed标志(main.zeek#L168-L174),确保兼容"先注册变更、后统一安装"的插件式过滤机制。
9.4 更多测试用例佐证
仓库中另有两条针对性测试: testing/btest/core/pcap/filter-error.zeek 与 testing/btest/core/pcap/input-error.zeek 覆盖了非法过滤器、读取输入失败等错误路径;testing/btest/opt/ZAM-bif-tracking.zeek 则验证了Pcap模块函数在 ZAM 优化下的行为。另外 src/script_opt/FuncInfo.cc#L133-L134 将Pcap::get_filter_state与Pcap::get_filter_state_string标记为ATTR_NO_ZEEK_SIDE_EFFECTS(无 Zeek 副作用),意味着编译器可以对它们进行更积极的优化——这是从源码结构可以推断的实现细节。
十、总结与最佳实践
围绕 pcap.bif.zeek.rst 文档所定义的函数族,Zeek 的Pcap模块提供了一套完整、包源无关的动态过滤能力:
- 两阶段过滤:
precompile_pcap_filter负责语法校验与编译(注意 ID 必须小于 100),install_pcap_filter负责生效,二者分离使运行时切换过滤器成为可能; - 状态可观测:
get_filter_state给出ok/fatal/warning三态判定,get_filter_state_string给出可读错误文本,配合Pcap::error可快速定位"编译失败"还是"安装失败"; - 框架级集成:
packet-filter框架将上述函数封装为default_capture_filter、restrict_filters、capture_filters、exclude等高层配置,普通用户通常无需直接调用Pcap函数,但理解其原理有助于排查packet_filter日志与相关 Notice; - 设备发现:
findalldevs在需要枚举本机网卡的脚本场景(如自动选择抓包接口)中可直接使用。
实践建议:当你在脚本中自定义动态过滤器时,优先复用packet-filter框架的exclude/exclude_for接口;只有需要精细控制多个独立过滤器的编译与安装时机时,才直接使用Pcap::precompile_pcap_filter与Pcap::install_pcap_filter,并记得为每个过滤器分配独立的PcapFilterID枚举值,同时检查返回值与get_filter_state_string的输出。
- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
相关推荐
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考