use anyhow::{bail, Result};
use chrono::NaiveDateTime;
use clap::{Parser, ValueHint};
use heck::ToShoutySnekCase;
use prost::Message;
use std::convert::TryFrom;
use std::fmt::Debug;
use std::io::Write;
use std::path::PathBuf;
use tokio::fs::File;
use tokio_stream::StreamExt;
use tokio_util::codec::Framed;
use dnstap_utils::dnstap;
use dnstap_utils::framestreams_codec::{Frame, FrameStreamsCodec};
use dnstap_utils::util::deserialize_dnstap_handler_error;
use dnstap_utils::util::fmt_dns_message;
use dnstap_utils::util::try_from_u8_slice_for_ipaddr;
use dnstap_utils::util::DnstapHandlerError;
#[derive(Parser, Debug)]
struct Opts {
#[clap(short = 'r',
long = "read",
name = "FILE",
value_parser,
value_hint = ValueHint::FilePath)
]
file: PathBuf,
}
#[tokio::main]
async fn main() -> Result<()> {
if cfg!(unix) {
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}
}
let opts = Opts::parse();
let file = File::open(opts.file).await?;
let mut framed = Framed::new(file, FrameStreamsCodec {});
while let Some(frame) = framed.next().await {
match frame {
Ok(frame) => match frame {
Frame::ControlReady(_) => {
bail!("Protocol error: READY frame not allowed here");
}
Frame::ControlAccept(_) => {
bail!("Protocol error: ACCEPT frame not allowed here");
}
Frame::ControlStart(_) => {
}
Frame::ControlStop => {
return Ok(());
}
Frame::ControlFinish => {
bail!("Protocol error: FINISH frame not allowed here");
}
Frame::ControlUnknown(_) => {
bail!("Protocol error: Unknown control frame");
}
Frame::Data(mut payload) => match dnstap::Dnstap::decode(&mut payload) {
Ok(d) => {
let mut s = String::with_capacity(2048);
fmt_dnstap(&mut s, d);
s.push_str("---\n");
std::io::stdout().write_all(s.as_bytes()).unwrap();
}
Err(e) => {
bail!(
"Protocol error: Decoding dnstap protobuf message: {}, payload: {}",
e,
hex::encode(&payload)
);
}
},
},
Err(e) => {
bail!("Protocol error: {}", e);
}
}
}
Ok(())
}
fn fmt_dnstap(s: &mut String, d: dnstap::Dnstap) {
if let Some(dtype) = dnstap::dnstap::Type::from_i32(d.r#type) {
s.push_str("type: ");
s.push_str(&format!("{:?}", dtype).TO_SHOUTY_SNEK_CASE());
s.push('\n');
}
if let Some(identity) = &d.identity {
s.push_str("identity: \"");
s.push_str(&String::from_utf8_lossy(identity));
s.push_str("\"\n");
}
if let Some(version) = &d.version {
s.push_str("version: \"");
s.push_str(&String::from_utf8_lossy(version));
s.push_str("\"\n");
}
if let Some(extra) = &d.extra {
s.push_str("extra:\n");
s.push_str(" bytes: \"");
s.push_str(&hex::encode(extra));
s.push_str("\"\n");
s.push_str(" type: ");
match deserialize_dnstap_handler_error(extra) {
Ok(dhe) => match dhe {
DnstapHandlerError::Mismatch(mismatch_dns_bytes, _, _) => {
s.push_str("DRDH_MISMATCH\n");
s.push_str(" mismatch_bytes: \"");
s.push_str(&hex::encode(&mismatch_dns_bytes));
s.push_str("\"\n");
s.push_str(" mismatch_formatted: |\n");
fmt_dns_message(s, " ", &mismatch_dns_bytes);
}
DnstapHandlerError::Timeout => {
s.push_str("DRDH_TIMEOUT\n");
}
DnstapHandlerError::MissingField => {
s.push_str("DRDH_MISSING_FIELD\n");
}
},
Err(_) => {
s.push_str("DRDH_EXTRA_PAYLOAD_PARSE_ERROR\n");
}
}
}
if let Some(msg) = &d.message {
fmt_dnstap_message(s, msg);
}
}
fn fmt_dnstap_message(s: &mut String, msg: &dnstap::Message) {
s.push_str("message:\n");
s.push_str(" type: ");
s.push_str(&format!("{:?}", msg.r#type()).TO_SHOUTY_SNEK_CASE());
s.push('\n');
if let Some(query_time_sec) = msg.query_time_sec {
if let Ok(query_time_sec) = i64::try_from(query_time_sec) {
if let Some(dt) =
NaiveDateTime::from_timestamp_opt(query_time_sec, msg.query_time_nsec())
{
s.push_str(" query_time: !!timestamp ");
s.push_str(&dt.to_string());
s.push('\n');
}
}
}
if let Some(response_time_sec) = msg.response_time_sec {
if let Ok(response_time_sec) = i64::try_from(response_time_sec) {
if let Some(dt) =
NaiveDateTime::from_timestamp_opt(response_time_sec, msg.response_time_nsec())
{
s.push_str(" response_time: !!timestamp ");
s.push_str(&dt.to_string());
s.push('\n');
}
}
}
if msg.socket_family.is_some() {
s.push_str(" socket_family: ");
s.push_str(&format!("{:?}", msg.socket_family()).TO_SHOUTY_SNEK_CASE());
s.push('\n');
}
if msg.socket_protocol.is_some() {
s.push_str(" socket_protocol: ");
s.push_str(&format!("{:?}", msg.socket_protocol()).TO_SHOUTY_SNEK_CASE());
s.push('\n');
}
if let Some(query_address) = &msg.query_address {
if let Ok(query_address) = try_from_u8_slice_for_ipaddr(query_address) {
s.push_str(" query_address: \"");
s.push_str(&query_address.to_string());
s.push_str("\"\n");
}
}
if let Some(response_address) = &msg.response_address {
if let Ok(response_address) = try_from_u8_slice_for_ipaddr(response_address) {
s.push_str(" response_address: \"");
s.push_str(&response_address.to_string());
s.push_str("\"\n");
}
}
if let Some(query_port) = &msg.query_port {
s.push_str(" query_port: ");
s.push_str(&query_port.to_string());
s.push('\n');
}
if let Some(response_port) = &msg.response_port {
s.push_str(" response_port: ");
s.push_str(&response_port.to_string());
s.push('\n');
}
if let Some(query_message) = &msg.query_message {
s.push_str(" query_message_bytes: \"");
s.push_str(&hex::encode(query_message));
s.push_str("\"\n");
s.push_str(" query_message_formatted: |\n");
fmt_dns_message(s, " ", query_message);
}
if let Some(response_message) = &msg.response_message {
s.push_str(" response_message_bytes: \"");
s.push_str(&hex::encode(response_message));
s.push_str("\"\n");
s.push_str(" response_message_formatted: |\n");
fmt_dns_message(s, " ", response_message);
}
}