Vector dnstap Source 深度实战:从 BIND 采集 DNSTAP DNS 查询日志的完整配置与解析机制
2026/9/13 20:12:24 网站建设 项目流程

Vector dnstap Source 深度实战:从 BIND 采集 DNSTAP DNS 查询日志的完整配置与解析机制

【免费下载链接】vectorA high-performance observability data pipeline.项目地址: https://gitcode.com/GitHub_Trending/vect/vector

本篇指南以 Vector 的dnstapsource 组件为核心,讲解如何配置 TCP/Unix Socket 两种监听模式从支持 DNSTAP 协议的 DNS 服务器(以 BIND 为例)采集 DNS 查询与动态更新日志,覆盖全部配置参数、输出事件字段结构、可运行的实操示例,并深入到 src/sources/dnstap/mod.rs 的帧处理与解析实现。读完你可以独立完成 DNS 服务器与 Vector 的对接,理解rawData解析链路和内部遥测事件的含义。

组件定位与核心属性

dnstapsource 用于从 dnstap)。它监听一条 socket 连接,接收 DNS 服务器(如 BIND)发出的 DNSTAP 帧(Protobuf 编码),将其解析为结构化的日志事件向下游输出。

组件元数据定义在 website/cue/reference/components/sources/dnstap.cue,关键属性如下:

属性说明
组件类型source数据入口
交付语义(delivery)best_effort尽力而为,不保证每条事件都送达
确认机制(acknowledgements)false不支持端到端确认(源码中can_acknowledge()返回false
部署角色daemon作为守护进程运行
开发状态beta仍处于 beta 阶段
有状态false无状态组件
输出数据logs输出日志事件,不支持 multiline

由于不支持确认且是尽力交付,生产环境建议将其用于可容忍少量丢失的 DNS 可观测性场景(查询分析、异常检测等),而非强一致性场景。

配置结构总览

配置结构由 src/sources/dnstap/mod.rs 中的DnstapConfig定义:顶层公共参数之外,通过mode字段(serde tag)在Tcp(TcpConfig)Unix(UnixConfig)两个变体之间切换。TcpConfigUnixConfig分别定义在 src/sources/dnstap/tcp.rs 和 src/sources/dnstap/unix.rs。参数说明数据(类型、默认值、适用条件、示例值)由 website/cue/reference/components/sources/generated/dnstap.cue 生成。

公共参数(tcp / unix 两种模式通用)

参数类型必填默认值说明
modestring (tcp|unix)unix 平台默认 unix,非 unix 平台默认 tcp(0.0.0.0:9000使用的 dnstap socket 类型
max_frame_lengthuint(字节)102400(100 KiB,见default_max_frame_length()源接受的最大 DNSTAP 帧长度,超过的帧直接丢弃
host_keystring全局log_schema.host_key覆盖写入对端地址的日志字段名,值为 socket 地址本身
raw_data_onlyboolfalse是否跳过 DNSTAP 帧的解析/解码。为true时原始帧数据以 base64 字符串写入事件的rawData字段
multithreadedboolfalse是否并发处理 DNSTAP 帧
max_frame_handling_tasksuint1000(源码中unwrap_or(1000)可并发处理的最大帧数
lowercase_hostnamesboolfalse是否将所有接收到的 DNSTAP 主机名统一转为小写,保证一致性

TCP 模式(mode = "tcp")

适用于跨主机接收 DNSTAP 数据,DNS 服务器通过网络连接到 Vector。

参数类型必填默认值说明
addressstring-监听地址,必须包含端口;支持systemd{#N}使用 systemd socket activation 传递的第 N 个 socket。示例:0.0.0.0:9000systemdsystemd#3
port_keystring"port"覆盖写入对端端口的日志字段名,值形如9000;设为""可抑制该字段
permit_originstring[]-允许的源 IP 网段白名单,CIDR 表示。示例:192.168.0.0/16127.0.0.1/32::1/128
receive_buffer_bytesuint(字节)-每条连接使用的接收缓冲区大小
max_connection_duration_secsuint(秒)-单条连接最长保持时间,超时的连接会被关闭,有助于对长连接做负载均衡
connection_limituint(连接数)-任意时刻允许的最大 TCP 连接数
keepalive对象-TCP keepalive 设置(TcpKeepaliveConfig
shutdown_timeout_secsuint(秒)30关闭阶段强制关闭连接前的超时时间
tls对象关闭TlsSourceConfig,开启 TLS 并支持从客户端证书中提取元数据

TCP 模式的一个关键细节来自 src/sources/dnstap/tcp.rs:当tls配置中指定了client_metadata_key时,DnstapFrameHandler::insert_tls_client_metadata会把客户端证书的subject写入事件的tls_client_metadata字段(键名可自定义),用于审计“哪个客户端连接在发送 DNSTAP 数据”。同时每个接收到的帧会触发SocketEventsReceived内部事件(mode = "tcp"),用于字节计数遥测。permit_origin在实现中映射为IpAllowlistConfig,最终转换为Vec<IpNet>网段表,不匹配的源地址连接会被拒绝。

Unix Socket 模式(mode = "unix")

适用于 Vector 与 DNS 服务器部署在同一台机器的最常见场景。DNS 服务器把 DNSTAP 数据写入 Vector 创建的 server UDS。

参数类型必填默认值说明
socket_pathstring/run/bind/dnstap.sock(源码UnixConfig::default()读取 DNSTAP 数据的 socket 文件绝对路径;源首次启动时若不存在会自动创建
socket_file_modeuintumask决定socket 文件的权限位。支持任意数字格式,最直观的是八进制,如0o7770o754508(即0o774
socket_receive_buffer_sizeuint(字节)-接收缓冲区大小。注意:需要相应调整系统级最大 socket 接收缓冲区(Linux 的/proc/sys/net/core/rmem_max)才能生效
socket_send_buffer_sizeuint(字节)-发送缓冲区大小。注意:需要相应调整/proc/sys/net/core/wmem_max

关于 UDS 的工作机制(源自 dnstap.cue 的 how_it_works 章节):

  • 启动时,dnstap源在指定路径创建新的 server UDS;若该路径上的 UDS 已存在(被占用),Vector 会先自动删除再创建
  • UDS 默认权限取决于当前umask。为了让本机 BIND 能够向 UDS 写数据,需通过socket_file_mode显式设置权限,例如:
[sources.my_dnstap_source] type = "dnstap" mode = "unix" socket_file_mode = 0o774 # 其他配置
  • 使用远端 BIND 服务器:UDS 只能创建在本地机器,但可以配合 SSH 端口/通道转发把本地 UDS 转发到远端 BIND 所在主机,让远端 BIND 写入“本地”socket。确保两端 Unix socket 权限设置正确。
  • 调整 UDS 缓冲区:在高负载场景下可平滑处理 DNS 流量尖峰,将socket_receive_buffer_size/socket_send_buffer_size调到例如 10 MiB:
[sources.my_dnstap_source] type = "dnstap" mode = "unix" socket_receive_buffer_size = 10_485_760 socket_send_buffer_size = 10_485_760 # 其他配置

事件输出字段结构

解析成功后,每个 DNSTAP 帧输出为一个日志事件。字段定义在 dnstap.cue 的 output 章节,并对应 src/sources/dnstap/mod.rs 中DnstapConfig::schema_definition引用的DnstapEventSchema(来自 lib/vector-vrl/dnstap-parser 的 schema 模块)。

顶层字段

字段类型必填说明
dataTypestringDNSTAP 事件数据类型,目前仅定义Message(载荷为 dnstap message)
dataTypeIduint数据类型数字 ID,如1
messageTypestringDNSTAP 消息类型(仅dataType = Message时有意义),枚举见下表
messageTypeIduint消息类型数字 ID,如6
timeuintDNS 消息发送/接收时间,为距 UNIX 纪元的timePrecision时间单位数
timePrecisionstringtime的时间精度:s/ms/us/ns
timestampstringtime相同时刻,ISO 8601 UTC 字符串,如2021-04-09T15:08:32.767098Z
serverIdstringDNS 服务器标识,如ns1.example.com
serverVersionstringDNS 服务器版本,如BIND 9.16.8
extraInfostring事件的附加数据(任意字节注解的 base64 形式)
socketFamilystringINET(IPv4)或INET6(IPv6),决定如何解释地址字段
socketProtocolstringUDPTCP,决定如何解释端口字段
sourceAddress/sourcePortstring / uint是 / 否消息发起方的网络地址 / 传输端口
responseAddress/responsePortstring / uint是 / 否消息响应方的网络地址 / 传输端口
errorstring解析 dnstap 数据失败时的错误信息
rawDatastring解析失败或开启raw_data_only时出现的原始 DNSTAP 二进制数据 base64
requestDataobjectDNS 查询/更新的请求消息数据,子结构见下
responseDataobjectDNS 查询/更新的响应消息数据,子结构见下

messageType 枚举(14 种视角组合)

枚举值语义(以谁为视角)
AuthQuery/AuthResponse权威服务器收到解析器的查询 / 权威服务器发回响应
ResolverQuery/ResolverResponse解析器发往权威服务器的查询(通常清 RD 位)/ 解析器收到的响应
ClientQuery/ClientResponseDNS 服务器视角:客户端发来递归期望的查询 / 服务器回给客户端的响应(通常置 RA 位)
ForwarderQuery/ForwarderResponse下游服务器发往上游递归服务器的查询 / 上游回给下游的响应
StubQuery/StubResponse桩解析器视角的查询 / 响应
ToolQuery/ToolResponseDNS 工具视角的查询 / 响应
UpdateQuery/UpdateResponse权威服务器视角收到的动态更新 / 发出的更新响应

requestData / responseData 子结构

  • 公共字段:timetimePrecisionfullRcode(4 位 header rcode + 8 位 opt extendedRcode 之和)、rcodeName(文本化响应码)、rawData(仅解析失败时出现,base64)。
  • rcodeName枚举覆盖 DNS 标准响应码:NoErrorFormErrServFailNXDomainNotImpRefusedYXDomainYXRRSetNXRRSetNotAuthNotZone,以及 EDNS 扩展码BADVERSBADSIGBADKEYBADTIMEBADMODEBADNAMEBADALGBADTRUNCBADCOOKIE
  • requestData特有子段:headerquestionadditionalopt(EDNS 伪段,含doednsVersionextendedRcodeoptionsudpPayloadSize)、zone/prerequisite/update(DNS 动态更新三段,见 RFC 2136)。
  • responseData特有子段:headerquestionanswersauthorityadditionaloptzone。其中opt还包含ede数组(扩展 DNS 错误,含infoCodepurposeextraText,见 RFC 8914)。
  • 资源记录条目字段示例:classdomainNamerDatarecordTyperecordTypeIdttl

实操示例一:采集常规 DNS 查询与响应

前置条件(来自 website/cue/reference/services/dnstap_data.cue 的设置说明):

  1. 参考 ISC 官方 KB 文章 “Using DNSTAP with BIND” 配置 BIND,使其把 DNSTAP 数据写入 Vector 将创建的 Unix socket;
  2. 确保该 socket 对 DNS 服务器进程可写(例如 BIND 对 socket 有rw权限,可用socket_file_mode = 5080o774实现);
  3. BIND 与 Vector 两端配置的 Unix socket 路径一致。

Vector 配置:

[sources.my_dnstap_source] type = "dnstap" mode = "unix" socket_path = "/run/bind/dnstap.sock" socket_file_mode = 508 max_frame_length = 102400 max_frame_handling_tasks = 10000

在 BIND 上执行一条本地查询:

nslookup host.example.com localhost

源将输出两个事件。查询事件(节选):

{ "dataType": "Message", "dataTypeId": 1, "messageType": "ClientQuery", "messageTypeId": 5, "requestData": { "fullRcode": 0, "header": { "aa": false, "ad": false, "anCount": 0, "arCount": 0, "cd": false, "id": 49653, "nsCount": 0, "opcode": 0, "qdCount": 1, "qr": 0, "ra": false, "rcode": 0, "rd": true, "tc": false }, "question": [ { "class": "IN", "domainName": "host.example.com.", "questionType": "A", "questionTypeId": 1 } ], "rcodeName": "NoError", "time": 1614781642516276825, "timePrecision": "ns" }, "responseAddress": "127.0.0.1", "responsePort": 0, "serverId": "ns1.example.com", "serverVersion": "BIND 9.16.8", "socketFamily": "INET", "socketProtocol": "UDP", "sourceAddress": "127.0.0.1", "sourcePort": 52398, "time": 1614781642516276825, "timePrecision": "ns" }

响应事件(节选):

{ "dataType": "Message", "dataTypeId": 1, "messageType": "ClientResponse", "messageTypeId": 6, "responseData": { "answers": [ { "class": "IN", "domainName": "host.example.com.", "rData": "192.0.2.100", "recordType": "A", "recordTypeId": 1, "ttl": 3600 } ], "authority": [ { "class": "IN", "domainName": "example.com.", "rData": "ns1.example.com.", "recordType": "NS", "recordTypeId": 2, "ttl": 86400 } ], "fullRcode": 0, "header": { "aa": true, "anCount": 1, "nsCount": 1, "qdCount": 1, "qr": 1, "ra": true, "rd": true, "id": 49653, "rcode": 0 }, "question": [ { "class": "IN", "domainName": "host.example.com.", "questionType": "A", "questionTypeId": 1 } ], "rcodeName": "NoError", "time": 1614781642516276825, "timePrecision": "ns" }, "responseAddress": "127.0.0.1", "responsePort": 0, "serverId": "ns1.example.com", "serverVersion": "BIND 9.16.8", "socketFamily": "INET", "socketProtocol": "UDP", "sourceAddress": "127.0.0.1", "sourcePort": 52398, "time": 1614781642516276825, "timePrecision": "ns" }

注意事项:BIND 需托管example.com区域且区域内含host.example.com主机记录;BIND 与 Vector 的 Unix socket 路径必须一致;BIND 对 socket 需有rw权限。

实操示例二:采集 DNS 动态更新(Dynamic Update)

若 DNS 流量较大,可把 UDS 缓冲区调大(注意同步调整系统级rmem_max/wmem_max):

[sources.my_dnstap_source] type = "dnstap" mode = "unix" socket_path = "/run/bind/dnstap.sock" socket_file_mode = 508 socket_receive_buffer_size = 10485760 socket_send_buffer_size = 10485760

对允许动态更新的权威 BIND 发送一条更新:

nsupdate <<EOF server localhost update add h1.example.com 3600 a 192.0.2.110 send EOF

源输出UpdateQuerymessageTypeId: 13)与UpdateResponsemessageTypeId: 14)两个事件。UpdateQueryrequestData包含opcode: 5upCount: 1zoCount: 1的更新 header,zone段(zName: example.com.zType: SOA)以及update段(domainName: h1.example.com.rData: 192.0.2.110ttl: 3600);UpdateResponseresponseDataqr: 1表示这是一条响应。注意事项与示例一相同,另需example.com区域允许动态更新。

源码级实现解析

帧处理主流程(mod.rs)

DnstapConfig::build(src/sources/dnstap/mod.rs)按mode分发:TCP 走build_framestream_tcp_source(附带MaybeTlsSettings),Unix 走build_framestream_unix_source(均来自 src/sources/util 的 framestream 工具)。核心帧处理逻辑集中在CommonFrameHandler

  • content_type固定为"protobuf:dnstap.Dnstap",framestream 层据此做 protobuf 拆帧;
  • handle_event中先发出BytesReceived(协议标记为 protobuf)内部事件做字节计数,再写入host(由host_key控制)等元数据;
  • raw_data_onlytrue:整帧 base64 编码后写入rawData字段,不做任何解码
  • 否则调用DnstapParser::parse(来自 lib/vector-vrl/dnstap-parser),并把DnsParserOptions { lowercase_hostnames }传入以支持统一小写主机名;解析失败时发出DnstapParseError内部事件并丢弃该帧(返回None),不会生成部分事件;
  • 最后按日志命名空间(legacy / vector)写入ingest_timestampsource_type = "dnstap"等标准源元数据。

解析失败的可观测性

解析错误事件定义在 src/internal_events/dnstap.rs:以error_type = PARSER_FAILEDstage = PROCESSING记录日志,并递增component_errors_total计数器。监控该计数器即可发现上游数据格式问题。配合事件中的errorrawData字段(解析失败时保留原始 base64 数据),可以事后离线重放排查。

集成测试的验证方式

mod.rs末尾的integration_tests模块(需dnstap-integration-testsfeature)演示了端到端验证方式:在 Docker 中运行 BIND,通过rndc dnstap -reopen让 BIND 重新打开 DNSTAP socket(路径如dnstap.sock2),再用nslookup/nsupdate触发真实查询与更新,最终断言输出事件中同时存在ClientQuery/ClientResponse(或UpdateQuery/UpdateResponse/AuthQuery/AuthResponse)以及requestData.question[0].domainNameresponseData.answers[0].rData等字段值正确。raw_data_only变体则断言所有事件都携带rawData字段。这套流程可以直接作为你自部署时的验收脚本参考。

相关能力:VRL 侧的 parse_dnstap 函数

仓库中 lib/vector-vrl/dnstap-parser/src/vrl_functions/parse_dnstap.rs 基于同一dnstap-parser库提供了 VRL 函数parse_dnstap(生成文档见 docs/generated/parse_dnstap.json)。若你在 source 层使用raw_data_only = true只落地原始 base64 数据,也可以后续在remap变换里用该函数按需解码,实现“先采集、后解析”的弹性方案。

使用注意事项小结

  • 该组件交付语义为 best effort、不支持端到端确认(can_acknowledge() == false),且开发状态为 beta,升级 Vector 版本时应关注其行为变化;
  • mode = "tcp"address必须带端口;跨机器部署建议配合permit_origin限制来源网段,高连接数场景用connection_limitmax_connection_duration_secs控制长连接;
  • max_frame_length过小会静默丢弃大帧,DNS over TCP 的大报文场景下需要评估并调大;
  • Unix 模式的socket_file_modesocket_receive/send_buffer_size与系统 umask、/proc/sys/net/core/{rmem,wmem}_max存在联动关系,调大缓冲区前请先确认系统上限;
  • 事件字段完整 schema 与枚举说明以 website/cue/reference/components/sources/dnstap.cue 和 generated/dnstap.cue 为准,下游管道编写 VRL 查询时可据此构造精确的字段路径。

【免费下载链接】vectorA high-performance observability data pipeline.项目地址: https://gitcode.com/GitHub_Trending/vect/vector

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

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

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

立即咨询