Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
15e23f4
Wip
Philip-NLnetLabs Oct 18, 2024
1f1c5a4
Initial version of xfr
Philip-NLnetLabs Oct 28, 2024
f67f1f7
Remove tracing_subscriber
Philip-NLnetLabs Dec 4, 2024
eeb7262
Remove tracing_subscriber.
Philip-NLnetLabs Dec 4, 2024
2e438ae
Small change to domain version.
Philip-NLnetLabs Dec 4, 2024
5b84e0b
Replace file path.
Philip-NLnetLabs Dec 4, 2024
c0a7b58
FIrst attempt at optionally supporting UDP -> TCP fallback for IXFR, …
ximon18 Mar 11, 2025
7f1a65e
Comment fix.
ximon18 Mar 11, 2025
2cedd5c
Comment fix.
ximon18 Mar 11, 2025
bb5628a
Initial attempt at proper RFC 1995 UDP -> TCP fallback.
ximon18 Mar 11, 2025
1f0507c
Use correct UDP transport.
ximon18 Mar 11, 2025
583685c
Fix panic.
ximon18 Mar 11, 2025
054bfd0
More debug.
ximon18 Mar 11, 2025
b11c5e7
FIX: Don't try to do multi-response UDP when falling back to TCP.
ximon18 Mar 11, 2025
b2ef88b
Prevent fallback to TCP if --notcp is supplied, which is useful for b…
ximon18 Mar 11, 2025
4b09e24
FIX: `cargo update log` to resolve compilation error "failed to selec…
ximon18 Mar 12, 2025
0dcad1e
Merge branch 'xfr' into xfr-with-udp
ximon18 Mar 12, 2025
d617609
Revert unrelated change to client.rs.
ximon18 Mar 12, 2025
831679d
Additional doc string for consistency.
ximon18 Mar 12, 2025
abac8b2
bump MSRV to 1.79 and fix domain dependency to a revision
tertsdiepraam Mar 17, 2025
491fcc2
fix return-let clippy lint
tertsdiepraam Mar 17, 2025
802ab8c
Merge pull request #45 from NLnetLabs/xfr-with-udp
tertsdiepraam Mar 18, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
rust: [1.78.0, stable, beta, nightly]
rust: [1.79.0, stable, beta, nightly]
env:
RUSTFLAGS: "-D warnings"
steps:
Expand Down
31 changes: 24 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "dnsi"
version = "0.2.0"
edition = "2021"
rust-version = "1.78.0"
rust-version = "1.79.0"
authors = ["NLnet Labs <[email protected]>"]
description = "A tool for investigating the DNS."
repository = "https://github.com/nlnetlabs/dnsi/"
Expand All @@ -16,7 +16,7 @@ exclude = [ ".github", ".gitignore" ]
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"]}
domain = { git = "https://github.com/NLnetLabs/domain.git", rev="e5ace3f1", 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" ] }
Expand Down
103 changes: 96 additions & 7 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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::{GetResponseMulti, RequestMessage, RequestMessageMulti, SendRequest, SendRequestMulti};
use domain::net::client::{dgram, stream};
use domain::resolv::stub::conf;
use std::fmt;
Expand Down Expand Up @@ -62,7 +62,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 All @@ -84,6 +84,25 @@ impl Client {
unreachable!()
}

pub async fn request_multi(
&self,
request: RequestMessageMulti<Vec<u8>>,
) -> Result<(Box<dyn GetResponseMulti>, Stats, Box<dyn SendRequestMulti<RequestMessageMulti<Vec<u8>>>>), Error> {
let mut servers = self.servers.as_slice();
while let Some((server, tail)) = servers.split_first() {
match self.request_server_multi(request.clone(), server).await {
Ok(get_response) => return Ok(get_response),
Err(err) => {
if tail.is_empty() {
return Err(err);
}
}
}
servers = tail;
}
unreachable!()
}

pub async fn request_server(
&self,
request: RequestMessage<Vec<u8>>,
Expand All @@ -97,6 +116,19 @@ impl Client {
}
}

pub async fn request_server_multi(
&self,
request: RequestMessageMulti<Vec<u8>>,
server: &Server,
) -> Result<(Box<dyn GetResponseMulti>, Stats, Box<dyn SendRequestMulti<RequestMessageMulti<Vec<u8>>>>), Error> {
match server.transport {
Transport::Udp => todo!(),
Transport::UdpTcp => todo!(),
Transport::Tcp => self.request_tcp_multi(request, server).await,
Transport::Tls => self.request_tls_multi(request, server).await,
}
}

pub async fn request_udptcp(
&self,
request: RequestMessage<Vec<u8>>,
Expand Down Expand Up @@ -132,16 +164,32 @@ 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(
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?;
let message = SendRequest::send_request(&conn, request).get_response().await?;
stats.finalize();
Ok(Answer { message, stats })
}

pub async fn request_tcp_multi(
&self,
request: RequestMessageMulti<Vec<u8>>,
server: &Server,
) -> Result<(Box<dyn GetResponseMulti>, Stats, Box<dyn SendRequestMulti<RequestMessageMulti<Vec<u8>>>>), Error> {
let stats = Stats::new(server.addr, Protocol::Tcp);
let socket = TcpStream::connect(server.addr).await?;
let (conn, tran) = stream::Connection::<RequestMessage<Vec<u8>>, _>::with_config(
socket,
Self::stream_config(server),
);
tokio::spawn(async { tran.run().await; print!("run terminated"); });
let get_resp = SendRequestMulti::send_request(&conn, request);
Ok((get_resp, stats, Box::new(conn)))
}

pub async fn request_tls(
&self,
request: RequestMessage<Vec<u8>>,
Expand Down Expand Up @@ -170,16 +218,53 @@ impl Client {
})?;
let tls_socket =
tls_connector.connect(server_name, tcp_socket).await?;
let (conn, tran) = stream::Connection::with_config(
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?;
let message = SendRequest::send_request(&conn, request).get_response().await?;
stats.finalize();
Ok(Answer { message, stats })
}

pub async fn request_tls_multi(
&self,
request: RequestMessageMulti<Vec<u8>>,
server: &Server,
) -> Result<(Box<dyn GetResponseMulti>, Stats, Box<dyn SendRequestMulti<RequestMessageMulti<Vec<u8>>>>), Error> {
let root_store = RootCertStore {
roots: webpki_roots::TLS_SERVER_ROOTS.into(),
};
let client_config = Arc::new(
ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth(),
);

let stats = Stats::new(server.addr, Protocol::Tls);
let tcp_socket = TcpStream::connect(server.addr).await?;
let tls_connector = tokio_rustls::TlsConnector::from(client_config);
let server_name = server
.tls_hostname
.clone()
.expect("tls_hostname must be set for tls")
.try_into()
.map_err(|_| {
let s = "Invalid DNS name";
<&str as Into<Error>>::into(s)
})?;
let tls_socket =
tls_connector.connect(server_name, tcp_socket).await?;
let (conn, tran) = stream::Connection::<RequestMessage<Vec<u8>>, _>::with_config(
tls_socket,
Self::stream_config(server),
);
tokio::spawn(tran.run());
let get_resp = SendRequestMulti::send_request(&conn, request);
Ok((get_resp, stats, Box::new(conn)))
}

fn dgram_config(server: &Server) -> dgram::Config {
let mut res = dgram::Config::new();
res.set_read_timeout(server.timeout);
Expand Down Expand Up @@ -235,6 +320,10 @@ pub struct Answer {
}

impl Answer {
pub fn new(message: Message<Bytes>, stats: Stats) -> Self {
Answer { message, stats }
}

pub fn stats(&self) -> Stats {
self.stats
}
Expand Down Expand Up @@ -274,7 +363,7 @@ impl Stats {
}
}

fn finalize(&mut self) {
pub fn finalize(&mut self) {
self.duration = Local::now() - self.start;
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pub mod help;
pub mod lookup;
pub mod query;
pub mod xfr;

use super::error::Error;

Expand All @@ -14,6 +15,9 @@ pub enum Command {
/// Lookup a host or address.
Lookup(self::lookup::Lookup),

/// Transfer a zone.
Xfr(self::xfr::Xfr),

/// Show the manual pages.
Help(self::help::Help),
}
Expand All @@ -23,6 +27,7 @@ impl Command {
match self {
Self::Query(query) => query.execute(),
Self::Lookup(lookup) => lookup.execute(),
Self::Xfr(xfr) => xfr.execute(),
Self::Help(help) => help.execute(),
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/commands/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ impl Query {
}
};

let answer = client.request(self.create_request()).await?;
let answer = client.request(self.create_request()?).await?;
self.output.format.print(&answer)?;
if self.verify {
let auth_answer = self.auth_answer().await?;
Expand Down Expand Up @@ -310,7 +310,7 @@ impl Query {
///
impl Query {
/// Creates a new request message.
fn create_request(&self) -> RequestMessage<Vec<u8>> {
fn create_request(&self) -> Result<RequestMessage<Vec<u8>>, Error> {
let mut res = MessageBuilder::new_vec();

res.header_mut().set_ad(self.ad);
Expand All @@ -320,12 +320,12 @@ 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)?;
if self.dnssec_ok {
// Avoid touching the EDNS Opt record unless we need to set DO.
req.set_dnssec_ok(true);
}
req
Ok(req)
}
}

Expand Down
Loading