1use clap::Parser;
2use std::net::SocketAddr;
3use std::path::PathBuf;
4use std::process;
5
6use dns_resolver::cache::SharedCache;
7use dns_resolver::resolve;
8use dns_resolver::util::types::{ProtocolMode, ResolvedRecord};
9use dns_types::protocol::types::{
10 DomainName, QueryClass, QueryType, Question, RecordClass, RecordType, ResourceRecord,
11};
12use dns_types::zones::types::Zone;
13use resolved::fs::load_zone_configuration;
14
15fn print_section(heading: &str, rrs: &[ResourceRecord]) {
16 if rrs.is_empty() {
17 return;
18 }
19
20 println!("\n;; {heading}");
21 for rr in rrs {
22 let rdata = Zone::default().serialise_rdata(&rr.rtype_with_data);
23 println!(
24 "{}\t{}\t{}\t{}\t{}",
25 rr.name,
26 rr.ttl,
27 rr.rclass,
28 rr.rtype_with_data.rtype(),
29 rdata
30 );
31 }
32}
33
34#[derive(Parser)]
36struct Args {
38 #[clap(value_parser)]
40 domain: DomainName,
41
42 #[clap(default_value_t = QueryType::Record(RecordType::A), value_parser)]
44 qtype: QueryType,
45
46 #[clap(long, action(clap::ArgAction::SetTrue))]
49 authoritative_only: bool,
50
51 #[clap(short, long, default_value_t = ProtocolMode::OnlyV4, value_parser)]
55 protocol_mode: ProtocolMode,
56
57 #[clap(long, default_value_t = 53, value_parser)]
60 upstream_dns_port: u16,
61
62 #[clap(short, long, value_parser)]
66 forward_address: Option<SocketAddr>,
67
68 #[clap(short = 'a', long, value_parser)]
70 hosts_file: Vec<PathBuf>,
71
72 #[clap(short = 'A', long, value_parser)]
75 hosts_dir: Vec<PathBuf>,
76
77 #[clap(short = 'z', long, value_parser)]
79 zone_file: Vec<PathBuf>,
80
81 #[clap(short = 'Z', long, value_parser)]
84 zones_dir: Vec<PathBuf>,
85}
86
87#[tokio::main]
88async fn main() {
89 let args = Args::parse();
90
91 let question = Question {
92 name: args.domain,
93 qtype: args.qtype,
94 qclass: QueryClass::Record(RecordClass::IN),
95 };
96
97 let zones = match load_zone_configuration(
98 &args.hosts_file,
99 &args.hosts_dir,
100 &args.zone_file,
101 &args.zones_dir,
102 )
103 .await
104 {
105 Some(zs) => zs,
106 None => {
107 eprintln!("could not load configuration");
108 process::exit(1);
109 }
110 };
111
112 println!(";; QUESTION");
113 println!("{}\t{}\t{}", question.name, question.qclass, question.qtype);
114
115 let (_, response) = resolve(
117 !args.authoritative_only,
118 args.protocol_mode,
119 args.upstream_dns_port,
120 args.forward_address,
121 &zones,
122 &SharedCache::new(),
123 &question,
124 )
125 .await;
126
127 match response {
128 Ok(response) => match response {
129 ResolvedRecord::Authoritative { rrs, soa_rr } => {
130 print_section("ANSWER", &rrs);
131 print_section("AUTHORITY", &[soa_rr]);
132 }
133 ResolvedRecord::AuthoritativeNameError { soa_rr } => {
134 println!("\n;; ANSWER");
135 println!("; name does not exist");
136 print_section("AUTHORITY", &[soa_rr]);
137 }
138 ResolvedRecord::NonAuthoritative { rrs, soa_rr } => {
139 print_section("ANSWER", &rrs);
140 if let Some(soa_rr) = soa_rr {
141 print_section("AUTHORITY", &[soa_rr]);
142 }
143 }
144 },
145 Err(err) => {
146 println!("\n;; ANSWER");
147 println!("; {err}");
148 process::exit(1);
149 }
150 }
151}