Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
929 changes: 588 additions & 341 deletions Cargo.lock

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ license = "BSD-3-Clause"
exclude = [ ".github", ".gitignore" ]

[dependencies]
bytes = "1"
clap = { version = "4", features = ["derive", "unstable-doc"] }
chrono = { version = "0.4.38", features = [ "alloc", "clock" ] }
domain = { version = "0.10", features = ["resolv", "unstable-client-transport"]}
tempfile = "3.1.0"
tokio = { version = "1.33", features = ["rt-multi-thread"] }
tokio-rustls = { version = "0.26.0", default-features = false, features = [ "ring", "logging", "tls12" ] }
bytes = "1"
clap = { version = "4", features = ["derive", "unstable-doc"] }
chrono = { version = "0.4.38", features = ["alloc", "clock", "serde"] }
domain = { version = "0.10", git = "https://github.com/NLnetLabs/domain.git", features = ["resolv", "unstable-client-transport", "serde"]}
serde = { version = "1.0.217", features = ["derive"] }
serde_json = { version = "1.0.135", features = ["preserve_order"] }
tempfile = "3.1.0"
tokio = { version = "1.33", features = ["rt-multi-thread"] }
tokio-rustls = { version = "0.26.0", default-features = false, features = ["ring", "logging", "tls12"] }
webpki-roots = "0.26.3"

[package.metadata.deb]
Expand Down
40 changes: 30 additions & 10 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ use domain::base::message_builder::MessageBuilder;
use domain::base::name::ToName;
use domain::base::question::Question;
use domain::net::client::protocol::UdpConnect;
use domain::net::client::request::{RequestMessage, SendRequest};
use domain::net::client::request::{
RequestMessage, RequestMessageMulti, SendRequest,
};
use domain::net::client::{dgram, stream};
use domain::resolv::stub::conf;
use serde::{Serialize, Serializer};
use std::fmt;
use std::net::SocketAddr;
use std::sync::Arc;
Expand Down Expand Up @@ -62,7 +65,7 @@ impl Client {
let mut res = res.question();
res.push(question.into()).unwrap();

self.request(RequestMessage::new(res)).await
self.request(RequestMessage::new(res)?).await
}

pub async fn request(
Expand Down Expand Up @@ -132,9 +135,11 @@ impl Client {
) -> Result<Answer, Error> {
let mut stats = Stats::new(server.addr, Protocol::Tcp);
let socket = TcpStream::connect(server.addr).await?;
let (conn, tran) = stream::Connection::with_config(
socket,
Self::stream_config(server),
let (conn, tran) = stream::Connection::<
_,
RequestMessageMulti<Vec<u8>>,
>::with_config(
socket, Self::stream_config(server)
);
tokio::spawn(tran.run());
let message = conn.send_request(request).get_response().await?;
Expand Down Expand Up @@ -170,9 +175,11 @@ impl Client {
})?;
let tls_socket =
tls_connector.connect(server_name, tcp_socket).await?;
let (conn, tran) = stream::Connection::with_config(
tls_socket,
Self::stream_config(server),
let (conn, tran) = stream::Connection::<
_,
RequestMessageMulti<Vec<u8>>,
>::with_config(
tls_socket, Self::stream_config(server)
);
tokio::spawn(tran.run());
let message = conn.send_request(request).get_response().await?;
Expand Down Expand Up @@ -256,14 +263,26 @@ impl AsRef<Message<Bytes>> for Answer {

//------------ Stats ---------------------------------------------------------

#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Serialize)]
pub struct Stats {
pub start: DateTime<Local>,
#[serde(serialize_with = "serialize_time_delta")]
pub duration: TimeDelta,
pub server_addr: SocketAddr,
pub server_proto: Protocol,
}

fn serialize_time_delta<S>(
t: &TimeDelta,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let msecs = t.num_milliseconds();
serializer.serialize_i64(msecs)
}

impl Stats {
fn new(server_addr: SocketAddr, server_proto: Protocol) -> Self {
Stats {
Expand All @@ -281,7 +300,8 @@ impl Stats {

//------------ Protocol ------------------------------------------------------

#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum Protocol {
Udp,
Tcp,
Expand Down
2 changes: 1 addition & 1 deletion src/commands/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ impl Query {
let mut res = res.question();
res.push((&self.qname.to_name(), self.qtype())).unwrap();

let mut req = RequestMessage::new(res);
let mut req = RequestMessage::new(res).unwrap();
if self.dnssec_ok {
// Avoid touching the EDNS Opt record unless we need to set DO.
req.set_dnssec_ok(true);
Expand Down
2 changes: 1 addition & 1 deletion src/output/friendly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ fn write_opt(
Expire(expire) => ("EXPIRE", expire.to_string()),
TcpKeepalive(opt) => ("TCPKEEPALIVE", opt.to_string()),
Padding(padding) => {
let padding = padding.as_slice();
let padding = padding.as_slice();
let len = padding.len();
let all_zero = if padding.iter().all(|b| *b == 0) {
"all zero"
Expand Down
135 changes: 135 additions & 0 deletions src/output/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use crate::client::{Answer, Stats};
use bytes::Bytes;
use domain::base::iana::{Class, Opcode};
use domain::base::{ParsedName, Rtype, Ttl};
use domain::rdata::AllRecordData;
use serde::Serialize;
use std::io;

use super::error::OutputError;

#[derive(Serialize)]
struct AnswerOuput {
message: MessageOutput,
stats: Stats,
}

#[derive(Serialize)]
struct MessageOutput {
id: u16,
qr: bool,
opcode: Opcode,
qdcount: u16,
ancount: u16,
nscount: u16,
arcount: u16,
question: QuestionOutput,
answer: Vec<RecordOutput>,
authority: Vec<RecordOutput>,
additional: Vec<RecordOutput>,
}

#[derive(Serialize)]
struct QuestionOutput {
name: String,
r#type: Rtype,
class: Class,
}

#[derive(Serialize)]
struct RecordOutput {
owner: String,
class: Class,
r#type: Rtype,
ttl: Ttl,
data: AllRecordData<Bytes, ParsedName<Bytes>>,
}

pub fn write(
answer: &Answer,
target: &mut impl io::Write,
) -> Result<(), OutputError> {
let msg = answer.message();
let stats = answer.stats();
let header = msg.header();
let counts = msg.header_counts();

let q = msg.question().next().unwrap().unwrap();

// We declare them all up front so that we have sensible defaults if the
// message turns out to be invalid.
let mut answer = Vec::new();
let mut authority = Vec::new();
let mut additional = Vec::new();

'outer: {
let Ok(section) = msg.answer() else {
break 'outer;
};

for rec in section.limit_to::<AllRecordData<_, _>>() {
let Ok(rec) = rec else {
break;
};

answer.push(RecordOutput {
owner: rec.owner().to_string(),
class: rec.class(),
r#type: rec.rtype(),
ttl: rec.ttl(),
data: rec.data().clone(),
});
}

let Ok(mut section) = msg.answer() else {
break 'outer;
};

for v in [&mut answer, &mut authority, &mut additional] {
let iter = section.limit_to::<AllRecordData<_, _>>();

for rec in iter {
let Ok(rec) = rec else {
break 'outer;
};

v.push(RecordOutput {
owner: format!("{}.", rec.owner()),
class: rec.class(),
r#type: rec.rtype(),
ttl: rec.ttl(),
data: rec.data().clone(),
});
}

let Ok(Some(s)) = section.next_section() else {
break;
};
section = s;
}
}

let output = AnswerOuput {
message: MessageOutput {
id: header.id(),
qr: header.qr(),
opcode: header.opcode(),
qdcount: counts.qdcount(),
ancount: counts.ancount(),
nscount: counts.nscount(),
arcount: counts.arcount(),
question: QuestionOutput {
name: format!("{}.", q.qname()),
r#type: q.qtype(),
class: q.qclass(),
},
answer,
authority,
additional,
},
stats,
};

serde_json::to_writer_pretty(target, &output).unwrap();
Ok(())
}
8 changes: 8 additions & 0 deletions src/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ mod ansi;
mod dig;
mod error;
mod friendly;
mod json;
mod rfc8427;
mod table;
mod table_writer;
mod ttl;
Expand All @@ -25,6 +27,10 @@ pub enum OutputFormat {

/// Short readable format
Table,
/// Simple JSON format
Json,
/// JSON based on RFC 8427
RFC8427,
}

#[derive(Clone, Debug, Parser)]
Expand All @@ -43,6 +49,8 @@ impl OutputFormat {
Self::Dig => self::dig::write(msg, target),
Self::Friendly => self::friendly::write(msg, target),
Self::Table => self::table::write(msg, target),
Self::Json => self::json::write(msg, target),
Self::RFC8427 => self::rfc8427::write(msg, target),
};
match res {
Ok(()) => Ok(()),
Expand Down
Loading
Loading