use crate::plugin::{Context, ExecPlugin, Plugin};
use crate::{RegisterExecPlugin, Result};
use async_trait::async_trait;
use std::fmt;
use std::sync::Arc;
use tracing::{debug, info};
const PLUGIN_DEBUG_PRINT_IDENTIFIER: &str = "debug_print";
#[derive(RegisterExecPlugin)]
pub struct DebugPrintPlugin {
print_queries: bool,
print_responses: bool,
prefix: String,
}
impl DebugPrintPlugin {
pub fn new() -> Self {
Self {
print_queries: true,
print_responses: true,
prefix: "DNS".to_string(),
}
}
pub fn queries_only() -> Self {
Self {
print_queries: true,
print_responses: false,
prefix: "DNS".to_string(),
}
}
pub fn responses_only() -> Self {
Self {
print_queries: false,
print_responses: true,
prefix: "DNS".to_string(),
}
}
pub fn with_prefix(mut self, prefix: String) -> Self {
self.prefix = prefix;
self
}
}
impl Default for DebugPrintPlugin {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for DebugPrintPlugin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DebugPrintPlugin")
.field("print_queries", &self.print_queries)
.field("print_responses", &self.print_responses)
.field("prefix", &self.prefix)
.finish()
}
}
#[async_trait]
impl Plugin for DebugPrintPlugin {
fn name(&self) -> &str {
PLUGIN_DEBUG_PRINT_IDENTIFIER
}
async fn execute(&self, ctx: &mut Context) -> Result<()> {
if self.print_queries {
let request = ctx.request();
if let Some(question) = request.questions().first() {
info!(
"{} Query: {} {:?} {:?}",
self.prefix,
question.qname(),
question.qtype(),
question.qclass()
);
}
}
if self.print_responses
&& let Some(response) = ctx.response()
{
info!(
"{} Response: {} answers, rcode={:?}",
self.prefix,
response.answers().len(),
response.response_code()
);
for (i, answer) in response.answers().iter().enumerate() {
debug!(
"{} Answer[{}]: {} {:?} ttl={}",
self.prefix,
i,
answer.name(),
answer.rtype(),
answer.ttl()
);
}
}
Ok(())
}
fn aliases() -> &'static [&'static str] {
&["debug", "print", "dbg"]
}
}
impl ExecPlugin for DebugPrintPlugin {
fn quick_setup(prefix: &str, exec_str: &str) -> Result<Arc<dyn Plugin>> {
if prefix != PLUGIN_DEBUG_PRINT_IDENTIFIER && !Self::aliases().contains(&prefix) {
return Err(crate::Error::Config(format!(
"ExecPlugin quick_setup: unsupported prefix '{}', expected one of {:?}",
prefix,
Self::aliases()
)));
}
let mut print_queries = false;
let mut print_responses = false;
let mut prefix_str = "DNS".to_string();
for part in exec_str.split(',') {
let part = part.trim();
if part == "queries" {
print_queries = true;
} else if part == "responses" {
print_responses = true;
} else if let Some(stripped) = part.strip_prefix("prefix=") {
prefix_str = stripped.to_string();
} else if part.is_empty() {
} else {
return Err(crate::Error::Config(format!(
"Invalid debug_print option: '{}'",
part
)));
}
}
if !print_queries && !print_responses {
print_queries = true;
print_responses = true;
}
let plugin = DebugPrintPlugin {
print_queries,
print_responses,
prefix: prefix_str,
};
Ok(Arc::new(plugin))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dns::types::{RecordClass, RecordType};
use crate::dns::{Message, Question, RData, ResourceRecord};
#[tokio::test]
async fn test_debug_print_queries_only() {
let plugin = DebugPrintPlugin::queries_only();
let mut request = Message::new();
request.add_question(Question::new(
"example.com".to_string(),
RecordType::A,
RecordClass::IN,
));
let mut ctx = Context::new(request);
assert!(plugin.execute(&mut ctx).await.is_ok());
}
#[tokio::test]
async fn test_debug_print_responses_only() {
let plugin = DebugPrintPlugin::responses_only();
let request = Message::new();
let mut ctx = Context::new(request);
let mut response = Message::new();
response.add_answer(ResourceRecord::new(
"example.com".to_string(),
RecordType::A,
RecordClass::IN,
300,
RData::A("192.0.2.1".parse().unwrap()),
));
ctx.set_response(Some(response));
assert!(plugin.execute(&mut ctx).await.is_ok());
}
#[tokio::test]
async fn test_debug_print_both() {
let plugin = DebugPrintPlugin::new();
let mut request = Message::new();
request.add_question(Question::new(
"example.com".to_string(),
RecordType::A,
RecordClass::IN,
));
let mut ctx = Context::new(request);
let mut response = Message::new();
response.add_answer(ResourceRecord::new(
"example.com".to_string(),
RecordType::A,
RecordClass::IN,
300,
RData::A("192.0.2.1".parse().unwrap()),
));
ctx.set_response(Some(response));
assert!(plugin.execute(&mut ctx).await.is_ok());
}
#[tokio::test]
async fn test_debug_print_custom_prefix() {
let plugin = DebugPrintPlugin::new().with_prefix("TEST".to_string());
let mut request = Message::new();
request.add_question(Question::new(
"example.com".to_string(),
RecordType::A,
RecordClass::IN,
));
let mut ctx = Context::new(request);
assert!(plugin.execute(&mut ctx).await.is_ok());
assert_eq!(plugin.prefix, "TEST");
}
#[test]
fn test_exec_plugin_quick_setup() {
let plugin =
<DebugPrintPlugin as ExecPlugin>::quick_setup("debug_print", "queries,responses")
.unwrap();
assert_eq!(plugin.name(), "debug_print");
let result = <DebugPrintPlugin as ExecPlugin>::quick_setup("invalid", "queries");
assert!(result.is_err());
let plugin =
<DebugPrintPlugin as ExecPlugin>::quick_setup("debug_print", "queries").unwrap();
if let Some(dp) = plugin.as_any().downcast_ref::<DebugPrintPlugin>() {
assert!(dp.print_queries);
assert!(!dp.print_responses);
}
let plugin =
<DebugPrintPlugin as ExecPlugin>::quick_setup("debug_print", "responses,prefix=TEST")
.unwrap();
if let Some(dp) = plugin.as_any().downcast_ref::<DebugPrintPlugin>() {
assert!(!dp.print_queries);
assert!(dp.print_responses);
assert_eq!(dp.prefix, "TEST");
}
}
}