1use std::path::PathBuf;
2
3use base64::prelude::{BASE64_STANDARD, Engine as _};
4use dnsmsg_parser::dns_message_parser::DnsParserOptions;
5use dnstap_parser::{
6 parser::DnstapParser,
7 schema::{DNSTAP_VALUE_PATHS, DnstapEventSchema},
8};
9use vector_lib::{
10 configurable::configurable_component,
11 event::{Event, LogEvent},
12 internal_event::{ByteSize, BytesReceived, InternalEventHandle, Protocol, Registered},
13 lookup::{owned_value_path, path},
14 tls::MaybeTlsSettings,
15};
16use vrl::{
17 path::{OwnedValuePath, PathPrefix},
18 value::{Kind, kind::Collection},
19};
20
21use super::util::framestream::{
22 FrameHandler, build_framestream_tcp_source, build_framestream_unix_source,
23};
24use crate::{
25 Result,
26 config::{DataType, SourceConfig, SourceContext, SourceOutput, log_schema},
27 internal_events::DnstapParseError,
28};
29
30pub mod tcp;
31#[cfg(unix)]
32pub mod unix;
33use vector_lib::{
34 config::{LegacyKey, LogNamespace},
35 lookup::lookup_v2::OptionalValuePath,
36};
37
38#[configurable_component(source("dnstap", "Collect DNS logs from a dnstap-compatible server."))]
40#[derive(Clone, Debug)]
41pub struct DnstapConfig {
42 #[serde(flatten)]
43 pub mode: Mode,
44
45 #[serde(default = "default_max_frame_length")]
49 #[configurable(metadata(docs::type_unit = "bytes"))]
50 pub max_frame_length: usize,
51
52 pub host_key: Option<OptionalValuePath>,
60
61 pub raw_data_only: Option<bool>,
66
67 pub multithreaded: Option<bool>,
69
70 pub max_frame_handling_tasks: Option<usize>,
72
73 #[serde(default = "crate::serde::default_false")]
75 pub lowercase_hostnames: bool,
76
77 #[configurable(metadata(docs::hidden))]
79 #[serde(default)]
80 pub log_namespace: Option<bool>,
81}
82
83fn default_max_frame_length() -> usize {
84 bytesize::kib(100u64) as usize
85}
86
87#[configurable_component]
89#[derive(Clone, Debug)]
90#[serde(tag = "mode", rename_all = "snake_case")]
91#[configurable(metadata(docs::enum_tag_description = "The type of dnstap socket to use."))]
92#[allow(clippy::large_enum_variant)] pub enum Mode {
94 Tcp(tcp::TcpConfig),
96
97 #[cfg(unix)]
99 Unix(unix::UnixConfig),
100}
101
102impl DnstapConfig {
103 pub fn new(socket_path: PathBuf) -> Self {
104 Self {
105 mode: Mode::Unix(unix::UnixConfig::new(socket_path)),
106 ..Default::default()
107 }
108 }
109
110 fn log_namespace(&self) -> LogNamespace {
111 self.log_namespace.unwrap_or(false).into()
112 }
113
114 fn raw_data_only(&self) -> bool {
115 self.raw_data_only.unwrap_or(false)
116 }
117
118 pub fn schema_definition(&self, log_namespace: LogNamespace) -> vector_lib::schema::Definition {
119 let event_schema = DnstapEventSchema;
120
121 match self.log_namespace() {
122 LogNamespace::Legacy => {
123 let schema = vector_lib::schema::Definition::empty_legacy_namespace();
124
125 if self.raw_data_only()
126 && let Some(message_key) = log_schema().message_key()
127 {
128 return schema.with_event_field(message_key, Kind::bytes(), Some("message"));
129 }
130 event_schema.schema_definition(schema)
131 }
132 LogNamespace::Vector => {
133 let schema = vector_lib::schema::Definition::new_with_default_metadata(
134 Kind::object(Collection::empty()),
135 [log_namespace],
136 )
137 .with_standard_vector_source_metadata();
138
139 if self.raw_data_only() {
140 schema.with_event_field(
141 &owned_value_path!("message"),
142 Kind::bytes(),
143 Some("message"),
144 )
145 } else {
146 event_schema.schema_definition(schema)
147 }
148 }
149 }
150 }
151}
152
153impl Default for DnstapConfig {
154 fn default() -> Self {
155 Self {
156 #[cfg(unix)]
157 mode: Mode::Unix(unix::UnixConfig::default()),
158 #[cfg(not(unix))]
159 mode: Mode::Tcp(tcp::TcpConfig::from_address(std::net::SocketAddr::new(
160 std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)),
161 9000,
162 ))),
163 max_frame_length: default_max_frame_length(),
164 host_key: None,
165 raw_data_only: None,
166 multithreaded: None,
167 max_frame_handling_tasks: None,
168 lowercase_hostnames: false,
169 log_namespace: None,
170 }
171 }
172}
173
174impl_generate_config_from_default!(DnstapConfig);
175
176#[async_trait::async_trait]
177#[typetag::serde(name = "dnstap")]
178impl SourceConfig for DnstapConfig {
179 async fn build(&self, cx: SourceContext) -> Result<super::Source> {
180 let log_namespace = cx.log_namespace(self.log_namespace);
181 let common_frame_handler = CommonFrameHandler::new(self, log_namespace);
182 match &self.mode {
183 Mode::Tcp(config) => {
184 let tls_config = config.tls().as_ref().map(|tls| tls.tls_config.clone());
185
186 let tls = MaybeTlsSettings::from_config(tls_config.as_ref(), true)?;
187 let frame_handler = tcp::DnstapFrameHandler::new(
188 config.clone(),
189 tls,
190 common_frame_handler,
191 log_namespace,
192 );
193
194 build_framestream_tcp_source(frame_handler, cx.shutdown, cx.out)
195 }
196 #[cfg(unix)]
197 Mode::Unix(config) => {
198 let frame_handler =
199 unix::DnstapFrameHandler::new(config.clone(), common_frame_handler);
200 build_framestream_unix_source(frame_handler, cx.shutdown, cx.out)
201 }
202 }
203 }
204
205 fn outputs(&self, global_log_namespace: LogNamespace) -> Vec<SourceOutput> {
206 let log_namespace = global_log_namespace.merge(Some(self.log_namespace()));
207 let schema_definition = self
208 .schema_definition(log_namespace)
209 .with_standard_vector_source_metadata();
210 vec![SourceOutput::new_maybe_logs(
211 DataType::Log,
212 schema_definition,
213 )]
214 }
215
216 fn can_acknowledge(&self) -> bool {
217 false
218 }
219}
220
221#[derive(Clone)]
222struct CommonFrameHandler {
223 max_frame_length: usize,
224 content_type: String,
225 raw_data_only: bool,
226 multithreaded: bool,
227 max_frame_handling_tasks: usize,
228 host_key: Option<OwnedValuePath>,
229 timestamp_key: Option<OwnedValuePath>,
230 source_type_key: Option<OwnedValuePath>,
231 bytes_received: Registered<BytesReceived>,
232 lowercase_hostnames: bool,
233 log_namespace: LogNamespace,
234}
235
236impl CommonFrameHandler {
237 pub fn new(config: &DnstapConfig, log_namespace: LogNamespace) -> Self {
238 let source_type_key = log_schema().source_type_key();
239 let timestamp_key = log_schema().timestamp_key();
240
241 let host_key = config
242 .host_key
243 .clone()
244 .map_or(log_schema().host_key().cloned(), |k| k.path);
245
246 Self {
247 max_frame_length: config.max_frame_length,
248 content_type: "protobuf:dnstap.Dnstap".to_string(),
249 raw_data_only: config.raw_data_only.unwrap_or(false),
250 multithreaded: config.multithreaded.unwrap_or(false),
251 max_frame_handling_tasks: config.max_frame_handling_tasks.unwrap_or(1000),
252 host_key,
253 timestamp_key: timestamp_key.cloned(),
254 source_type_key: source_type_key.cloned(),
255 bytes_received: register!(BytesReceived::from(Protocol::from("protobuf"))),
256 lowercase_hostnames: config.lowercase_hostnames,
257 log_namespace,
258 }
259 }
260}
261
262impl FrameHandler for CommonFrameHandler {
263 fn content_type(&self) -> String {
264 self.content_type.clone()
265 }
266
267 fn max_frame_length(&self) -> usize {
268 self.max_frame_length
269 }
270
271 fn handle_event(
272 &self,
273 received_from: Option<vrl::prelude::Bytes>,
274 frame: vrl::prelude::Bytes,
275 ) -> Option<vector_lib::event::Event> {
276 self.bytes_received.emit(ByteSize(frame.len()));
277
278 let mut log_event = LogEvent::default();
279
280 if let Some(host) = received_from {
281 self.log_namespace.insert_source_metadata(
282 DnstapConfig::NAME,
283 &mut log_event,
284 self.host_key.as_ref().map(LegacyKey::Overwrite),
285 path!("host"),
286 host,
287 );
288 }
289
290 if self.raw_data_only {
291 log_event.insert(
292 (PathPrefix::Event, &DNSTAP_VALUE_PATHS.raw_data),
293 BASE64_STANDARD.encode(&frame),
294 );
295 } else if let Err(err) = DnstapParser::parse(
296 &mut log_event,
297 frame,
298 DnsParserOptions {
299 lowercase_hostnames: self.lowercase_hostnames,
300 },
301 ) {
302 emit!(DnstapParseError {
303 error: format!("Dnstap protobuf decode error {err:?}.")
304 });
305 return None;
306 }
307
308 if self.log_namespace == LogNamespace::Vector {
309 self.log_namespace.insert_vector_metadata(
311 &mut log_event,
312 self.timestamp_key(),
313 path!("ingest_timestamp"),
314 chrono::Utc::now(),
315 );
316 }
317
318 self.log_namespace.insert_vector_metadata(
319 &mut log_event,
320 self.source_type_key(),
321 path!("source_type"),
322 DnstapConfig::NAME,
323 );
324
325 Some(Event::from(log_event))
326 }
327
328 fn multithreaded(&self) -> bool {
329 self.multithreaded
330 }
331
332 fn max_frame_handling_tasks(&self) -> usize {
333 self.max_frame_handling_tasks
334 }
335
336 fn host_key(&self) -> &Option<vrl::path::OwnedValuePath> {
337 &self.host_key
338 }
339
340 fn timestamp_key(&self) -> Option<&vrl::path::OwnedValuePath> {
341 self.timestamp_key.as_ref()
342 }
343
344 fn source_type_key(&self) -> Option<&vrl::path::OwnedValuePath> {
345 self.source_type_key.as_ref()
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use vector_lib::event::{Event, LogEvent};
352 use vrl::event_path;
353
354 use super::*;
355
356 #[test]
357 fn simple_matches_schema() {
358 let record = r#"{"dataType":"Message",
359 "dataTypeId":1,
360 "messageType":"ClientQuery",
361 "messageTypeId":5,
362 "requestData":{
363 "fullRcode":0,
364 "header":{
365 "aa":false,
366 "ad":true,
367 "anCount":0,
368 "arCount":1,
369 "cd":false,
370 "id":38339,
371 "nsCount":0,
372 "opcode":0,
373 "qdCount":1,
374 "qr":0,
375 "ra":false,
376 "rcode":0,
377 "rd":true,
378 "tc":false},
379 "opt":{
380 "do":false,
381 "ednsVersion":0,
382 "extendedRcode":0,
383 "options":[{"optCode":10,
384 "optName":"Cookie",
385 "optValue":"5JiWq4VYa7U="}],
386 "udpPayloadSize":1232},
387 "question":[{"class":"IN","domainName":"whoami.example.org.","questionType":"A","questionTypeId":1}],
388 "rcodeName":"NoError",
389 "time":1667909880863224758,
390 "timePrecision":"ns"},
391 "serverId":"stephenwakely-Precision-5570",
392 "serverVersion":"CoreDNS-1.10.0",
393 "socketFamily":"INET",
394 "socketProtocol":"UDP",
395 "sourceAddress":"0.0.0.0",
396 "sourcePort":54782,
397 "source_type":"dnstap",
398 "time":1667909880863224758,
399 "timePrecision":"ns"
400 }"#;
401
402 let json: serde_json::Value = serde_json::from_str(record).unwrap();
403 let mut event = Event::from(LogEvent::from(vrl::value::Value::from(json)));
404 event
405 .as_mut_log()
406 .insert(event_path!("timestamp"), chrono::Utc::now());
407
408 let definition = DnstapEventSchema;
409 let schema = vector_lib::schema::Definition::empty_legacy_namespace()
410 .with_standard_vector_source_metadata();
411
412 definition
413 .schema_definition(schema)
414 .assert_valid_for_event(&event)
415 }
416}
417
418#[cfg(all(test, feature = "dnstap-integration-tests"))]
419mod integration_tests {
420 #![allow(clippy::print_stdout)] use bollard::{
423 Docker,
424 exec::{CreateExecOptions, StartExecOptions},
425 };
426 use futures::StreamExt;
427 use serde_json::json;
428 use tokio::time;
429 use vector_lib::{event::Event, lookup::lookup_v2::OptionalValuePath};
430 use vrl::event_path;
431
432 use self::unix::UnixConfig;
433 use super::*;
434 use crate::{
435 SourceSender,
436 event::Value,
437 test_util::{
438 components::{SOURCE_TAGS, assert_source_compliance},
439 wait_for,
440 },
441 };
442
443 async fn test_dnstap(raw_data: bool, query_type: &'static str) {
444 assert_source_compliance(&SOURCE_TAGS, async {
445 let (sender, mut recv) = SourceSender::new_test();
446
447 tokio::spawn(async move {
448 let socket = get_socket(raw_data, query_type);
449
450 DnstapConfig {
451 mode: Mode::Unix(UnixConfig {
452 socket_path: socket,
453 socket_file_mode: Some(511),
454 socket_receive_buffer_size: Some(10485760),
455 socket_send_buffer_size: Some(10485760),
456 }),
457 max_frame_length: 102400,
458 host_key: Some(OptionalValuePath::from(owned_value_path!("key"))),
459 raw_data_only: Some(raw_data),
460 multithreaded: Some(false),
461 max_frame_handling_tasks: Some(100000),
462 lowercase_hostnames: false,
463 log_namespace: None,
464 }
465 .build(SourceContext::new_test(sender, None))
466 .await
467 .unwrap()
468 .await
469 .unwrap()
470 });
471
472 send_query(raw_data, query_type);
473
474 let event = time::timeout(time::Duration::from_secs(10), recv.next())
475 .await
476 .expect("fetch dnstap source event timeout")
477 .expect("failed to get dnstap source event from a stream");
478 let mut events = vec![event];
479 loop {
480 match time::timeout(time::Duration::from_secs(1), recv.next()).await {
481 Ok(Some(event)) => events.push(event),
482 Ok(None) => {
483 println!("None: No event");
484 break;
485 }
486 Err(e) => {
487 println!("Error: {e}");
488 break;
489 }
490 }
491 }
492
493 verify_events(raw_data, query_type, &events);
494 })
495 .await;
496 }
497
498 fn send_query(raw_data: bool, query_type: &'static str) {
499 tokio::spawn(async move {
500 let socket_path = get_socket(raw_data, query_type);
501 let (query_port, control_port) = get_bind_ports(raw_data, query_type);
502
503 wait_for(move || {
506 let path = socket_path.clone();
507 async move { path.exists() }
508 })
509 .await;
510
511 reload_bind_dnstap_socket(control_port).await;
513
514 match query_type {
515 "query" => {
516 nslookup(query_port).await;
517 }
518 "update" => {
519 nsupdate().await;
520 }
521 _ => (),
522 }
523 });
524 }
525
526 fn verify_events(raw_data: bool, query_event: &'static str, events: &[Event]) {
527 if raw_data {
528 assert_eq!(events.len(), 2);
529 assert!(
530 events
531 .iter()
532 .all(|v| v.as_log().get(event_path!("rawData")).is_some()),
533 "No rawData field!"
534 );
535 } else if query_event == "query" {
536 assert_eq!(events.len(), 2);
537 assert!(
538 events
539 .iter()
540 .any(|v| v.as_log().get(event_path!("messageType"))
541 == Some(&Value::Bytes("ClientQuery".into()))),
542 "No ClientQuery event!"
543 );
544 assert!(
545 events
546 .iter()
547 .any(|v| v.as_log().get(event_path!("messageType"))
548 == Some(&Value::Bytes("ClientResponse".into()))),
549 "No ClientResponse event!"
550 );
551 } else if query_event == "update" {
552 assert_eq!(events.len(), 4);
553 assert!(
554 events
555 .iter()
556 .any(|v| v.as_log().get(event_path!("messageType"))
557 == Some(&Value::Bytes("UpdateQuery".into()))),
558 "No UpdateQuery event!"
559 );
560 assert!(
561 events
562 .iter()
563 .any(|v| v.as_log().get(event_path!("messageType"))
564 == Some(&Value::Bytes("UpdateResponse".into()))),
565 "No UpdateResponse event!"
566 );
567 assert!(
568 events
569 .iter()
570 .any(|v| v.as_log().get(event_path!("messageType"))
571 == Some(&Value::Bytes("AuthQuery".into()))),
572 "No UpdateQuery event!"
573 );
574 assert!(
575 events
576 .iter()
577 .any(|v| v.as_log().get(event_path!("messageType"))
578 == Some(&Value::Bytes("AuthResponse".into()))),
579 "No UpdateResponse event!"
580 );
581 }
582
583 for event in events {
584 let json = serde_json::to_value(event.as_log().all_event_fields().unwrap()).unwrap();
585 match query_event {
586 "query" => {
587 if json["messageType"] == json!("ClientQuery") {
588 assert_eq!(
589 json["requestData.question[0].domainName"],
590 json!("h1.example.com.")
591 );
592 assert_eq!(json["requestData.rcodeName"], json!("NoError"));
593 } else if json["messageType"] == json!("ClientResponse") {
594 assert_eq!(
595 json["responseData.answers[0].domainName"],
596 json!("h1.example.com.")
597 );
598 assert_eq!(json["responseData.answers[0].rData"], json!("10.0.0.11"));
599 assert_eq!(json["responseData.rcodeName"], json!("NoError"));
600 }
601 }
602 "update" => {
603 if json["messageType"] == json!("UpdateQuery") {
604 assert_eq!(
605 json["requestData.update[0].domainName"],
606 json!("dh1.example.com.")
607 );
608 assert_eq!(json["requestData.update[0].rData"], json!("10.0.0.21"));
609 assert_eq!(json["requestData.rcodeName"], json!("NoError"));
610 } else if json["messageType"] == json!("UpdateResponse") {
611 assert_eq!(json["responseData.rcodeName"], json!("NoError"));
612 }
613 }
614 _ => (),
615 }
616 }
617 }
618
619 fn get_container() -> String {
620 std::env::var("CONTAINER_NAME").unwrap_or_else(|_| "vector_dnstap".into())
621 }
622
623 fn get_socket(raw_data: bool, query_type: &'static str) -> PathBuf {
624 let socket_folder = std::env::var("BIND_SOCKET")
625 .map(PathBuf::from)
626 .expect("BIND socket directory must be specified via BIND_SOCKET");
627
628 match query_type {
629 "query" if raw_data => socket_folder.join("dnstap.sock1"),
630 "query" => socket_folder.join("dnstap.sock2"),
631 "update" => socket_folder.join("dnstap.sock3"),
632 _ => unreachable!("no other test variants should exist"),
633 }
634 }
635
636 fn get_bind_ports(raw_data: bool, query_type: &'static str) -> (&'static str, &'static str) {
637 match query_type {
639 "query" if raw_data => ("8001", "9001"),
640 "query" => ("8002", "9002"),
641 "update" => ("8003", "9003"),
642 _ => unreachable!("no other test variants should exist"),
643 }
644 }
645
646 async fn dnstap_exec(cmd: Vec<&str>) {
647 let docker = Docker::connect_with_defaults().expect("failed binding to docker socket");
648 let config = CreateExecOptions {
649 cmd: Some(cmd),
650 attach_stdout: Some(true),
651 attach_stderr: Some(true),
652 ..Default::default()
653 };
654 let result = docker
655 .create_exec(get_container().as_str(), config)
656 .await
657 .expect("failed to execute command");
658 docker
659 .start_exec(&result.id, None::<StartExecOptions>)
660 .await
661 .expect("failed to execute command");
662 }
663
664 async fn reload_bind_dnstap_socket(control_port: &str) {
665 dnstap_exec(vec![
666 "/usr/sbin/rndc",
667 "-p",
668 control_port,
669 "dnstap",
670 "-reopen",
671 ])
672 .await
673 }
674
675 async fn nslookup(port: &str) {
676 dnstap_exec(vec![
677 "nslookup",
678 "-type=A",
679 format!("-port={port}").as_str(),
680 "h1.example.com",
681 "localhost",
682 ])
683 .await
684 }
685
686 async fn nsupdate() {
687 dnstap_exec(vec!["nsupdate", "-v", "/bind3/etc/bind/nsupdate.txt"]).await
688 }
689
690 #[tokio::test]
691 async fn test_dnstap_raw_event() {
692 test_dnstap(true, "query").await;
693 }
694
695 #[tokio::test]
696 async fn test_dnstap_query_event() {
697 test_dnstap(false, "query").await;
698 }
699
700 #[tokio::test]
701 async fn test_dnstap_update_event() {
702 test_dnstap(false, "update").await;
703 }
704}