#![warn(
clippy::default_trait_access,
clippy::dbg_macro,
clippy::unimplemented,
missing_copy_implementations,
missing_docs,
non_snake_case,
non_upper_case_globals,
rust_2018_idioms,
unreachable_pub
)]
use std::{
net::{IpAddr, SocketAddr},
path::PathBuf,
time::Instant,
};
use clap::Parser;
use console::style;
use trust_dns_client::op::Query;
use trust_dns_recursor::Recursor;
use trust_dns_resolver::{
config::{NameServerConfig, NameServerConfigGroup, Protocol},
proto::rr::RecordType,
Name,
};
#[derive(Debug, Parser)]
#[clap(name = "recurse")]
struct Opts {
domainname: Name,
#[clap(short = 't', long = "type", default_value = "A")]
ty: RecordType,
#[clap(short = 'n', long, use_value_delimiter = true, value_delimiter(','))]
nameserver: Vec<SocketAddr>,
#[clap(long)]
bind: Option<IpAddr>,
#[clap(long)]
ipv4: bool,
#[clap(long)]
ipv6: bool,
#[clap(long)]
udp: bool,
#[clap(long)]
tcp: bool,
#[clap(long)]
debug: bool,
#[clap(long)]
info: bool,
#[clap(long)]
warn: bool,
#[clap(long)]
error: bool,
#[clap(short = 'r', long)]
roots: Option<PathBuf>,
}
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let opts: Opts = Opts::parse();
let log_level = if opts.debug {
Some(tracing::Level::DEBUG)
} else if opts.info {
Some(tracing::Level::INFO)
} else if opts.warn {
Some(tracing::Level::WARN)
} else if opts.error {
Some(tracing::Level::ERROR)
} else {
None
};
trust_dns_util::logger(env!("CARGO_BIN_NAME"), log_level);
let mut roots = NameServerConfigGroup::new();
for socket_addr in &opts.nameserver {
roots.push(NameServerConfig {
socket_addr: *socket_addr,
protocol: Protocol::Tcp,
tls_dns_name: None,
trust_negative_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: opts.bind.map(|ip| SocketAddr::new(ip, 0)),
});
roots.push(NameServerConfig {
socket_addr: *socket_addr,
protocol: Protocol::Udp,
tls_dns_name: None,
trust_negative_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: opts.bind.map(|ip| SocketAddr::new(ip, 0)),
});
}
let ipv4 = opts.ipv4 || !opts.ipv6;
let ipv6 = opts.ipv6 || !opts.ipv4;
let udp = opts.udp || !opts.tcp;
let tcp = opts.tcp || !opts.udp;
roots.retain(|ns| (ipv4 && ns.socket_addr.is_ipv4()) || (ipv6 && ns.socket_addr.is_ipv6()));
roots.retain(|ns| {
(udp && ns.protocol == Protocol::Udp) || (tcp && ns.protocol == Protocol::Tcp)
});
let name = opts.domainname;
let ty = opts.ty;
let recursor = Recursor::new(roots)?;
println!(
"Recursing for {name} {ty} from roots",
name = style(&name).yellow(),
ty = style(ty).yellow(),
);
let now = Instant::now();
let query = Query::query(name, ty);
let lookup = recursor.resolve(query, now).await?;
println!(
"{} for query {:?}",
style("Success").green(),
style(&lookup).blue()
);
for r in lookup.record_iter().filter(|r| r.record_type() == ty) {
print!(
"\t{name} {ttl} {class} {ty}",
name = style(r.name()).blue(),
ttl = style(r.ttl()).blue(),
class = style(r.dns_class()).blue(),
ty = style(r.record_type()).blue(),
);
if let Some(rdata) = r.data() {
println!(" {rdata}");
} else {
println!("NULL")
}
}
Ok(())
}