☰
Zeek DNS 协议分析模块全解析:base/protocols/dns 的加载结构、常量映射与查询响应跟踪机制
2026/10/10 15:33:58 网站建设 项目流程
  • 网络安全
  • 网络
  • 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 仓库中base/protocols/dns脚本包(含 index.rst 所描述的__load__.zeek、consts.zeek、main.zeek、check-event-handlers.zeek四个文件)为核心,系统讲解 Zeek 如何对 Domain Name System (DNS) 协议进行开箱即用的分析:从脚本包的加载顺序、DNS 数值码点(QTYPE/QCLASS/RCCODE/Opcode 等)到人类可读字符串的映射表,再到日志记录DNS::Info的字段定义、查询/响应的配对跟踪状态机与可调优的运行时选项。读完本文,你将能够读懂dns.log的每一列来源,知道如何通过redef/option调整 DNS 分析行为,并能依据源码级证据扩展自己的 DNS 分析脚本。

脚本包结构:四个文件如何协同工作

Zeek 的脚本包以__load__.zeek为统一入口。scripts/base/protocols/dns/__load__.zeek的内容非常精简,仅有三行@load指令:

@load ./consts @load ./main @load ./check-event-handlers

也就是说,加载base/protocols/dns等价于按以下顺序加载三个子模块:

文件职责
consts.zeek定义 DNS 分析所需的类型(数值码点)、错误码与字段常量,是"helper file",供其他 DNS 分析脚本复用
main.zeek基础 DNS 分析脚本:跟踪并记录 DNS 查询及其响应,定义日志流DNS::LOG与核心数据结构
check-event-handlers.zeek校验脚本:当某些 DNS 事件处理器因配置原因永远不会被触发时,向用户发出警告

三个文件均声明module DNS;,所有导出符号都位于DNS::命名空间下。consts.zeek是纯数据层(只定义常量与映射表),main.zeek是逻辑层(注册日志流、挂接事件、维护配对状态),check-event-handlers.zeek是健康检查层,职责边界非常清晰。

consts.zeek:DNS 数值码点的权威映射表

consts.zeek(源码)是理解dns.log中qtype_name、qclass_name、rcode_name、opcode_name等可读字段的唯一事实来源。这些字段之所以能自动变成字符串,正是因为每张映射表都带有&default兜底函数:当遇到表中未收录的新码点时,会返回query-N、rcode-N、qclass-N这类占位字符串,保证日志字段永不缺失。

标量常量

常量值语义
DNS::PTR12RR TYPE 值,域名的指针记录(反向解析)
DNS::EDNS41OPT 伪资源记录的 RR TYPE 值,代表 EDNS(0) 扩展
DNS::NONE254表示"无类别"的 class 值,用于动态更新(RFC 2136)
DNS::ANY255QTYPE 值,表示请求所有记录

操作码(Opcode)常量

consts.zeek定义了DNS::DNS_OP_QUERY = 0、DNS::DNS_OP_IQUERY = 1、DNS::DNS_OP_SERVER_STATUS = 2、DNS::DNS_OP_NOTIFY = 4、DNS::DNS_OP_DYNAMIC_UPDATE = 5、DNS::DNS_OP_DSO = 6,并配套两张映射表:

  • DNS::opcodes:标准 DNS 操作码 → 可读名,如[0]="query"、[5]="dynamic-update"、[6]="dso";
  • DNS::netbios_opcodes:NetBIOS Name Service(NBNS,RFC 1002)专用操作码 → 可读名,如[0]="netbios-query"、[5]="netbios-registration"、[8]="netbios-refresh"。

main.zeek的set_session钩子中正是依据msg$is_netbios标志来决定查哪张表,从而正确渲染 NetBIOS 查询的 opcode 名称。

查询类型表DNS::query_types

这是 Zeek 维护的一张覆盖面很广的 QTYPE 映射表(源码位置),从经典类型到 DNSSEC、再到现代加密 DNS 类型均有收录。节选如下(完整定义以源码为准):

码点名称码点名称
1A28AAAA
2NS33SRV
5CNAME35NAPTR
6SOA43DS
12PTR46RRSIG
15MX48DNSKEY
16TXT64SVCB
41OPT65HTTPS
250TSIG257CAA
252AXFR255*(任意类型)

此外还收录了WINS/WINS-R(65281/65282)、INTEGRITY(65521)等私有或扩展码点。dns_request事件处理中,qtype_name直接通过query_types[qtype]获得。

类别表DNS::classes与错误码表DNS::base_errors

  • DNS::classes覆盖 CLASS/QCLASS 字段:[1]="C_INTERNET"、[2]="C_CSNET"、[3]="C_CHAOS"、[4]="C_HESIOD"、[254]="C_NONE"、[255]="C_ANY";
  • DNS::base_errors覆盖除 TSIG/EDNS 之外的标准 RCODE:[0]="NOERROR"、[1]="FORMERR"、[2]="SERVFAIL"、[3]="NXDOMAIN"、[4]="NOTIMP"、[5]="REFUSED"、[6]="YXDOMAIN"、[8]="NXRRSet"、[9]="NOTAUTH"、[16]="BADVERS"、[17]="BADKEY"、[18]="BADTIME"、[19]="BADMODE"、[23]="BADCOOKIE"、[3842]="BADSIG"等,未分配码点(11–15)显式标注为unassigned-N。

DNSSEC 相关表

  • DNS::algorithms:DNSKEY/DS/RRSIG 记录中的签名算法,如[1]="RSA_MD5"、[5]="RSA_SHA1"、[8]="RSA_SHA256"、[10]="RSA_SHA512"、[13]="ECDSA_curveP256withSHA256"、[15]="Ed25519"、[16]="Ed448"、[254]="PrivateOID";
  • DNS::digests:DS 记录使用的摘要类型,如[1]="SHA1"、[2]="SHA256"、[3]="GOST_R_34_11_94"、[4]="SHA384";
  • DNS::edns_zfield:EDNS 扩展头中 Z 字段的取值,[0]="NOVALUE"、[32768]="DNS_SEC_OK"(表示接受 DNSSEC RR)。

SVCB/HTTPS 参数表DNS::svcparam_keys

DNS::svcparam_keys依据 RFC 9460 定义 SVCB/HTTPS 记录的 SvcParam 键:[0]="mandatory"、[1]="alpn"、[2]="no-default-alpn"、[3]="port"、[4]="ipv4hint"、[5]="ech"、[6]="ipv6hint"。源码注释明确要求"Keep in sync with src/analyzer/protocol/dns/DNS.h SVCPARAM_Key",即与 C++ 分析器侧的枚举保持一致,这也是验证映射正确性的关键线索。

main.zeek:查询/响应跟踪与 dns.log 的生成

main.zeek(源码)是整个 DNS 分析的核心。它的主要工作分为三层:注册日志流、维护连接级配对状态、通过事件/钩子持续填充日志记录。

日志流与端口注册

redef enum Log::ID += { LOG }; const ports = { 53/udp, 53/tcp, 137/udp, 5353/udp, 5355/udp } &redef; event zeek_init() &priority=5 { Log::create_stream(DNS::LOG, Log::Stream($columns=Info, $ev=log_dns, $path="dns", $policy=log_policy)); Analyzer::register_for_ports(Analyzer::ANALYZER_DNS, ports); }

要点:

  • 新增枚举DNS::LOG作为日志流标识;
  • 创建日志流时指定$columns=Info(列结构)、$ev=log_dns(写日志前触发的可观察事件)、$path="dns"(即dns.log)、$policy=log_policy(默认策略钩子);
  • 通过Analyzer::register_for_ports让 DNS 分析器监听一组"众所周知"端口,默认集合为{ 53/udp, 53/tcp, 137/udp, 5353/udp, 5355/udp },覆盖标准 DNS、NetBIOS(137)、mDNS(5353) 与 LLMNR(5355)。这是一个&redef常量,可通过redef DNS::ports += { ... };扩充。

日志记录结构DNS::Info

DNS::Info是dns.log的列定义(源码位置),逐列含义如下:

字段类型是否记录语义
tstime是该连接上最早观测到 DNS 协议消息的时间
uidstring是传输 DNS 消息的连接的唯一标识
idconn_id是连接的 4 元组(两端地址/端口)
prototransport_proto是传输层协议(UDP/TCP)
trans_idcount是(可选)发起方分配的 16 位事务 ID,用于配对响应
rttinterval是(可选)查询到响应开始之间的往返时延
querystring是(可选)查询的域名
qclass/qclass_namecount / string是(可选)查询类别值及其可读名
qtype/qtype_namecount / string是(可选)查询类型值及其可读名
rcode/rcode_namecount / string是(可选)响应码值及其可读名
AA/TC/RD/RAbool是(默认 F)权威应答、截断、期望递归、可用递归四个标志位
Zcount是(默认 0)RFC 1035 规定的 3 位保留字段(DNSSEC 时可能非零)
answersvector of string是(可选)应答区中的资源描述集合
TTLsvector of interval是(可选)与answers对应的缓存生存期
rejectedbool是(默认 F)查询是否被服务器拒绝
opcode/opcode_namecount / string是(可选)请求/响应的操作码值及可读名
total_answerscount否(可选)应答区资源记录总数
total_repliescount否(可选)应答+权威+附加区资源记录总数
saw_query/saw_replybool否(默认 F)是否已观测到完整查询/响应

另有两个仅当加载特定 policy 脚本时才出现的可选字段:加载 scripts/policy/protocols/dns/auth-addl.zeek 后会出现auth(权威应答)与addl(附加应答);加载 scripts/policy/protocols/dns/log-original-query-case.zeek 后会出现original_query(保留原始大小写的查询名)。这种"字段按需扩展"的机制是 Zeek 脚本生态的典型模式。

连接状态DNS::State与待配对消息队列

DNS::State(源码位置)按连接跟踪 DNS 查询状态:

type State: record { pending_query: Info &optional; # 尚未配对的单条查询(性能快路径) pending_queries: PendingMessages &optional; # 按事务 ID 索引的未配对查询队列 pending_replies: PendingMessages &optional; # 按事务 ID 索引的未配对响应队列 };

其中PendingMessages是table[count] of Queue::Queue——即以事务 ID 为键、值为base/utils/queue.zeek提供的 FIFO 队列。之所以引入队列而非单条记录,是因为同一连接上可能并发出现多个共享同一事务 ID 的查询/响应(例如大型 AXFR 区域传输过程中会有大量同 ID 消息)。

该状态通过redef record connection += { dns: Info &optional; dns_state: State &optional; };挂接到每个连接记录上,并在set_session钩子中随连接生命周期创建、通过Conn::register_removal_hook(c, finalize_dns)注册连接移除回调。

配对逻辑:set_session钩子

set_session钩子(源码位置)是查询/响应配对的核心,由dns_message事件以&priority=5触发,并传入is_query = ! msg$QR。其分支逻辑可以归纳为:

  1. 多播目的地短路:若响应方地址属于DNS::multicast_subnets(默认{ 224.0.0.0/4, ff00::/8 }),则不做配对,直接将saw_query与saw_reply置真,让dns_end立即记录该消息。这是因为 mDNS/LLMNR 的响应来自不同连接,按事务 ID 配对会得到错误结果;
  2. 查询方向:若该事务 ID 已有待配对响应,则直接取队列头部响应与之配对;否则新建会话并放入pending_query(快路径)或pending_queries队列;
  3. 响应方向:优先与pending_query中同 ID 的查询配对;否则查pending_queries队列;仍无匹配则放入pending_replies等待后续查询;
  4. 填充公共字段:响应方向补充rcode/rcode_name、total_answers、total_replies、rejected(rcode != 0且无查询消息时置真);双向都填充opcode/opcode_name。

超限兜底:两个运行时选项

为防止异常流量(如服务器损坏、Zeek 丢包、AXFR 进行中)导致内存膨胀,main.zeek提供两个可用option调整的阈值(enqueue_new_msg函数中生效):

  • DNS::max_pending_msgs(默认50):单个事务 ID 的待配对查询/响应达到该数量后,放弃继续匹配,先把队列中已有的未配对消息全部写日志再清空重建;
  • DNS::max_pending_query_ids(默认50):待配对查询/响应横跨的不同事务 ID 数量达到该值后,认为这些消息永远无法配对,整体记录后丢弃。

应答收集:do_reply钩子与各dns_*_reply事件

main.zeek为每种资源记录类型实现了对应的dns_*_reply事件处理器(均以&priority=5挂接),它们把 RR 的具体内容格式化为字符串后统一交给DNS::do_reply钩子:

  • dns_A_reply/dns_AAAA_reply/dns_A6_reply:输出地址字符串;对动态更新消息额外带上ans$query前缀;
  • dns_TXT_reply/dns_SPF_reply:逐条输出TXT 长度 内容/SPF 长度 内容,多条以空格连接;
  • dns_NS_reply、dns_CNAME_reply、dns_MX_reply、dns_PTR_reply、dns_SRV_reply:输出名称/目标;
  • dns_SOA_reply:输出soa$mname(主域名服务器);dns_NAPTR_reply:编码 order/preference/flags/service/regexp/replacement;
  • DNSSEC 系列:dns_RRSIG(RRSIG 覆盖类型 签名者,根签名者显示为<Root>)、dns_DNSKEY(算法)、dns_NSEC(NSEC 查询 下一名称)、dns_NSEC3/dns_NSEC3PARAM、dns_DS(算法与摘要类型)、dns_SSHFP(十六进制指纹)、dns_LOC(size/精度);
  • 动态更新:dns_dynamic_update_pre与dns_dynamic_update_del依据 qclass/qtype 组合生成NameNotInUse、NameInUse、NoRRSet、RRSetExists、RRSet *、RR ...等描述。

DNS::do_reply钩子(默认实现)在收集阶段完成以下工作:填充query(若尚未设置)、AA/RA标志、计算rtt(network_time() - c$dns$ts,为 0 时删除以免误报)、追加answers与TTLs。do_reply是一个hook,意味着用户脚本可以在其默认实现之前/之后插入额外逻辑。

记录时机:dns_end与finalize_dns

  • dns_end事件(&priority=5)负责置位saw_query/saw_reply;另一重载(&priority=-5)在两者均为真时执行Log::write(DNS::LOG, c$dns)并清除,从而保证"查询与响应成对后只记一行";
  • DNS::finalize_dns是Conn::RemovalHook,在连接状态到期时被调用,把仍滞留的pending_query、pending_queries、pending_replies全部写日志,避免状态被丢弃导致日志缺失。

事件与钩子速查

符号类型用途
DNS::log_dnsevent(rec: Info)每条记录写入日志框架前触发,供用户脚本访问/修改DNS::Info
DNS::set_sessionhook(c, msg, is_query)每次建立 DNS 会话(查询或响应)时调用,可做附加初始化
DNS::do_replyhook(c, msg, ans, reply)各类dns_*_reply收集应答的统一入口
DNS::log_policyLog::PolicyHook日志流的默认策略钩子
DNS::finalize_dnsConn::RemovalHook连接移除时冲刷未配对消息

check-event-handlers.zeek:预防"静默失效"的事件处理器校验

check-event-handlers.zeek 的职责非常独特:当全局选项dns_skip_all_addl为真时,附加区(additional section)相关事件不会被分析器触发,如果用户脚本仍然使用了这些事件,会产生"看起来没问题、实际上从不执行"的静默失效。该脚本在zeek_init(&priority=20,早于一般脚本)中检查dns_TSIG_addl、dns_EDNS_addl、dns_EDNS_ecs、dns_EDNS_tcp_keepalive、dns_EDNS_cookie五个事件是否被用户处理,是则输出Reporter::warning;同时警告dns_TKEY事件在dns_skip_all_addl下ans参数将不含数据。这体现了 Zeek 脚本框架中"可观测性优先"的设计习惯——宁可显式警告,也不让脚本静默失效。

结合 policy 脚本的扩展用法

base层的 DNS 分析默认只记录基础列,仓库在scripts/policy/protocols/dns/目录提供以下按需加载的增强脚本:

  • auth-addl.zeek:为DNS::Info增加auth与addl字段,记录权威区与附加区的应答;
  • log-original-query-case.zeek:增加original_query字段,保留查询名的原始大小写(对检测基于大小写差异的隐蔽通道有价值);
  • detect-external-names.zeek 与 disable-opcode-log-fields.zeek:前者检测外部域名解析,后者可在不需要 opcode 字段时缩减日志体积。

典型使用方式是在local.zeek或自定义脚本中@load policy/protocols/dns/auth-addl。这些脚本通过redef扩展DNS::Info记录或挂接DNS::log_policy,展示了 base 层为上层策略预留的扩展点。

实战验证与调试建议

  1. 跑通基线:对任意包含 DNS 流量的 pcap 运行zeek -r capture.pcap(或zeek -C -r ...),检查生成的dns.log是否包含qtype_name、rcode_name等可读列,并对照 doc/scripts/base/protocols/dns/main.zeek.rst 中DNS::Info的字段定义逐一核对;
  2. 观察配对行为:对正常流量检查rtt与trans_id的对应关系;对 mDNS/LLMNR 流量(多播地址)应能看到逐条独立记录而非配对记录,这正是multicast_subnets逻辑生效的表现;
  3. 调整分析范围:若需监控自定义端口上的 DNS,使用redef DNS::ports += { 5353/udp };;若担心异常流量导致队列膨胀,可调低DNS::max_pending_msgs或DNS::max_pending_query_ids;
  4. 扩展日志字段:在脚本中hook DNS::log_dns(或直接event DNS::log_dns)读取/改写rec,即可在记录落盘前附加自定义信息;需要权威/附加区信息时加载policy/protocols/dns/auth-addl。

从源码结构看,base/protocols/dns的设计目标可以概括为:以 consts 提供"数字 ↔ 语义"的确定性翻译,以 main 提供健壮的跨连接配对状态机,以 hook/event 暴露全部扩展点,再以 check-event-handlers 兜底配置陷阱——四个文件各司其职,共同构成 Zeek 对 DNS 协议分析的开箱即用能力。

  • 网络安全
  • 网络
  • 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
点击查看免费下载

相关推荐

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

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

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

立即咨询