dnsq/
main.rs

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// the doc comments for this struct turn into the CLI help text
35#[derive(Parser)]
36/// DNS recursive lookup utility
37struct Args {
38    /// Domain name to resolve
39    #[clap(value_parser)]
40    domain: DomainName,
41
42    /// Query type to resolve
43    #[clap(default_value_t = QueryType::Record(RecordType::A), value_parser)]
44    qtype: QueryType,
45
46    /// Only answer queries for which this configuration is authoritative: do
47    /// not perform recursive or forwarding resolution
48    #[clap(long, action(clap::ArgAction::SetTrue))]
49    authoritative_only: bool,
50
51    /// How to choose between connecting to upstream nameservers over IPv4 or
52    /// IPv6 when acting as a recursive resolver: one of 'only-v4', 'prefer-v4',
53    /// 'prefer-v6', 'only-v6'
54    #[clap(short, long, default_value_t = ProtocolMode::OnlyV4, value_parser)]
55    protocol_mode: ProtocolMode,
56
57    /// Which port to query upstream nameservers over when acting as a recursive
58    /// resolver
59    #[clap(long, default_value_t = 53, value_parser)]
60    upstream_dns_port: u16,
61
62    /// Act as a forwarding resolver, not a recursive resolver: forward queries
63    /// which can't be answered from local state to this nameserver (in
64    /// `ip:port` form)
65    #[clap(short, long, value_parser)]
66    forward_address: Option<SocketAddr>,
67
68    /// Path to a hosts file, can be specified more than once
69    #[clap(short = 'a', long, value_parser)]
70    hosts_file: Vec<PathBuf>,
71
72    /// Path to a directory to read hosts files from, can be specified more than
73    /// once
74    #[clap(short = 'A', long, value_parser)]
75    hosts_dir: Vec<PathBuf>,
76
77    /// Path to a zone file, can be specified more than once
78    #[clap(short = 'z', long, value_parser)]
79    zone_file: Vec<PathBuf>,
80
81    /// Path to a directory to read zone files from, can be specified more than
82    /// once
83    #[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    // TODO: log upstream queries as they happen
116    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}