Files
hickory-dns/util/src/resolve.rs
2022-08-24 15:20:43 -07:00

268 lines
7.4 KiB
Rust

// Copyright 2015-2020 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! The resolve program
// BINARY WARNINGS
#![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};
use clap::Parser;
use console::style;
use trust_dns_resolver::config::{
NameServerConfig, NameServerConfigGroup, Protocol, ResolverConfig, ResolverOpts,
};
use trust_dns_resolver::proto::rr::RecordType;
use trust_dns_resolver::TokioAsyncResolver;
/// A CLI interface for the trust-dns-resolver.
///
/// This utility directly uses the trust-dns-resolver to perform a lookup to a
/// set of nameservers. Many of the features can be directly tested via the
/// FLAGS and OPTIONS. By default (like trust-dns-resolver) the configured
/// nameservers are the Google provided ones. The system configured ones can be
/// used with the `--system` FLAG. Other nameservers, as many as desired, can
/// be configured directly with the `--nameserver` OPTION.
#[derive(Debug, Parser)]
#[clap(name = "resolve")]
struct Opts {
/// Name to attempt to resolve, if followed by a '.' then it's a fully-qualified-domain-name.
domainname: String,
/// Type of query to issue, e.g. A, AAAA, NS, etc.
#[clap(short = 't', long = "type", default_value = "A")]
ty: RecordType,
/// Happy eye balls lookup, ipv4 and ipv6
#[clap(short = 'e', long = "happy", conflicts_with("ty"))]
happy: bool,
/// Use system configuration, e.g. /etc/resolv.conf, instead of defaults
#[clap(short = 's', long = "system")]
system: bool,
/// Use google resolvers, default
#[clap(long)]
google: bool,
/// Use cloudflare resolvers
#[clap(long)]
cloudflare: bool,
/// Use quad9 resolvers
#[clap(long)]
quad9: bool,
/// Specify a nameserver to use, ip and port e.g. 8.8.8.8:53 or \[2001:4860:4860::8888\]:53 (port required)
#[clap(
short = 'n',
long,
use_value_delimiter = true,
require_value_delimiter = true
)]
nameserver: Vec<SocketAddr>,
/// Specify the IP address to connect from.
#[clap(long)]
bind: Option<IpAddr>,
/// Use ipv4 addresses only, default is both ipv4 and ipv6
#[clap(long)]
ipv4: bool,
/// Use ipv6 addresses only, default is both ipv4 and ipv6
#[clap(long)]
ipv6: bool,
/// Use only UDP, default to UDP and TCP
#[clap(long)]
udp: bool,
/// Use only TCP, default to UDP and TCP
#[clap(long)]
tcp: bool,
/// Enable debug and all logging
#[clap(long)]
debug: bool,
/// Enable info + warning + error logging
#[clap(long)]
info: bool,
/// Enable warning + error logging
#[clap(long)]
warn: bool,
/// Enable error logging
#[clap(long)]
error: bool,
}
/// Run the resolve program
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let opts: Opts = Opts::parse();
// enable logging early
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);
// read system configuration
let (sys_config, sys_options): (Option<ResolverConfig>, Option<ResolverOpts>) = if opts.system {
let (config, options) = trust_dns_resolver::system_conf::read_system_conf()?;
(Some(config), Some(options))
} else {
(None, None)
};
// Configure all the name servers
let mut name_servers = NameServerConfigGroup::new();
for socket_addr in &opts.nameserver {
name_servers.push(NameServerConfig {
socket_addr: *socket_addr,
protocol: Protocol::Tcp,
tls_dns_name: None,
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: opts.bind.map(|ip| SocketAddr::new(ip, 0)),
});
name_servers.push(NameServerConfig {
socket_addr: *socket_addr,
protocol: Protocol::Udp,
tls_dns_name: None,
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: opts.bind.map(|ip| SocketAddr::new(ip, 0)),
});
}
if opts.google {
name_servers.merge(NameServerConfigGroup::google());
}
if opts.cloudflare {
name_servers.merge(NameServerConfigGroup::cloudflare());
}
if opts.quad9 {
name_servers.merge(NameServerConfigGroup::quad9());
}
if name_servers.is_empty() && sys_config.is_none() {
name_servers.merge(NameServerConfigGroup::google());
}
let ipv4 = opts.ipv4 || !opts.ipv6;
let ipv6 = opts.ipv6 || !opts.ipv4;
let udp = opts.udp || !opts.tcp;
let tcp = opts.tcp || !opts.udp;
name_servers
.retain(|ns| (ipv4 && ns.socket_addr.is_ipv4()) || (ipv6 && ns.socket_addr.is_ipv6()));
name_servers.retain(|ns| {
(udp && ns.protocol == Protocol::Udp) || (tcp && ns.protocol == Protocol::Tcp)
});
let mut config = sys_config.unwrap_or_else(ResolverConfig::new);
for ns in name_servers.iter() {
config.add_name_server(ns.clone());
}
let name_servers = config.name_servers().iter().map(|n| format!("{}", n)).fold(
String::new(),
|mut names, n| {
if !names.is_empty() {
names.push_str(", ")
}
names.push_str(&n);
names
},
);
// query parameters
let name = &opts.domainname;
let ty = opts.ty;
// configure the resolver options
let mut options = sys_options.unwrap_or_default();
if opts.happy {
options.ip_strategy = trust_dns_resolver::config::LookupIpStrategy::Ipv4AndIpv6;
}
let resolver = TokioAsyncResolver::tokio(config, options)?;
// execute query
println!(
"Querying for {name} {ty} from {ns}",
name = style(name).yellow(),
ty = style(ty).yellow(),
ns = style(name_servers).blue()
);
let lookup = if opts.happy {
let lookup = resolver.lookup_ip(name.to_string()).await?;
lookup.into()
} else {
resolver.lookup(name.to_string(), ty).await?
};
// report response, TODO: better display of errors
println!(
"{} for query {}",
style("Success").green(),
style(lookup.query()).blue()
);
for r in lookup.record_iter() {
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}", rdata = rdata);
} else {
println!("NULL")
}
}
Ok(())
}