use clap::Parser;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::process;
use dns_resolver::cache::SharedCache;
use dns_resolver::resolve;
use dns_resolver::util::types::{ProtocolMode, ResolvedRecord};
use dns_types::protocol::types::{
DomainName, QueryClass, QueryType, Question, RecordClass, RecordType, ResourceRecord,
};
use dns_types::zones::types::Zone;
use resolved::fs::load_zone_configuration;
fn print_section(heading: &str, rrs: &[ResourceRecord]) {
if rrs.is_empty() {
return;
}
println!("\n;; {heading}");
for rr in rrs {
let rdata = Zone::default().serialise_rdata(&rr.rtype_with_data);
println!(
"{}\t{}\t{}\t{}\t{}",
rr.name,
rr.ttl,
rr.rclass,
rr.rtype_with_data.rtype(),
rdata
);
}
}
#[derive(Parser)]
struct Args {
#[clap(value_parser)]
domain: DomainName,
#[clap(default_value_t = QueryType::Record(RecordType::A), value_parser)]
qtype: QueryType,
#[clap(long, action(clap::ArgAction::SetTrue))]
authoritative_only: bool,
#[clap(short, long, default_value_t = ProtocolMode::OnlyV4, value_parser)]
protocol_mode: ProtocolMode,
#[clap(long, default_value_t = 53, value_parser)]
upstream_dns_port: u16,
#[clap(short, long, value_parser)]
forward_address: Option<SocketAddr>,
#[clap(short = 'a', long, value_parser)]
hosts_file: Vec<PathBuf>,
#[clap(short = 'A', long, value_parser)]
hosts_dir: Vec<PathBuf>,
#[clap(short = 'z', long, value_parser)]
zone_file: Vec<PathBuf>,
#[clap(short = 'Z', long, value_parser)]
zones_dir: Vec<PathBuf>,
}
#[tokio::main]
async fn main() {
let args = Args::parse();
let question = Question {
name: args.domain,
qtype: args.qtype,
qclass: QueryClass::Record(RecordClass::IN),
};
let zones = match load_zone_configuration(
&args.hosts_file,
&args.hosts_dir,
&args.zone_file,
&args.zones_dir,
)
.await
{
Some(zs) => zs,
None => {
eprintln!("could not load configuration");
process::exit(1);
}
};
println!(";; QUESTION");
println!("{}\t{}\t{}", question.name, question.qclass, question.qtype);
let (_, response) = resolve(
!args.authoritative_only,
args.protocol_mode,
args.upstream_dns_port,
args.forward_address,
&zones,
&SharedCache::new(),
&question,
)
.await;
match response {
Ok(response) => match response {
ResolvedRecord::Authoritative { rrs, soa_rr } => {
print_section("ANSWER", &rrs);
print_section("AUTHORITY", &[soa_rr]);
}
ResolvedRecord::AuthoritativeNameError { soa_rr } => {
println!("\n;; ANSWER");
println!("; name does not exist");
print_section("AUTHORITY", &[soa_rr]);
}
ResolvedRecord::NonAuthoritative { rrs, soa_rr } => {
print_section("ANSWER", &rrs);
if let Some(soa_rr) = soa_rr {
print_section("AUTHORITY", &[soa_rr]);
}
}
},
Err(err) => {
println!("\n;; ANSWER");
println!("; {err}");
process::exit(1);
}
}
}