foctet_net/dns/
mod.rs

1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
2
3use anyhow::Result;
4use foctet_core::id::NodeId;
5use hickory_resolver::config::{ResolverConfig, ResolverOpts};
6use hickory_resolver::error::ResolveError;
7use hickory_resolver::system_conf;
8use hickory_resolver::TokioAsyncResolver;
9use hickory_resolver::{
10    lookup::{Ipv4Lookup, Ipv6Lookup, TxtLookup},
11    lookup_ip::LookupIp,
12};
13use stackaddr::{Protocol, StackAddr};
14
15/// The prefix for `NodeId` TXT record lookups.
16const DNS_NODEID_PREFIX: &str = "_nodeid.";
17
18/// The result of a successful resolution of a `Resolver`.
19pub enum Resolved {
20    /// The given name has been resolved to a single `IpAddr`.
21    One(IpAddr),
22    /// The given name has been resolved to multiple `IpAddr`s.
23    Many(Vec<IpAddr>),
24    /// The given name has been resolved to a new list of `NodeId`s
25    /// obtained from DNS TXT records representing possible alternatives.
26    NodeIds(Vec<NodeId>),
27}
28
29#[derive(Debug, Clone)]
30pub enum ResolverOption {
31    Default,
32    System,
33    Cloudflare,
34    Google,
35    Quad9,
36    Custom(ResolverConfig, ResolverOpts),
37}
38
39pub struct Resolver {
40    resolver: TokioAsyncResolver,
41}
42
43impl Resolver {
44    /// Creates a new [`Resolver`] with the default resolver configuration (Cloudflare) and options.
45    pub fn new() -> Result<Self> {
46        Ok(Self {
47            resolver: TokioAsyncResolver::tokio(
48                ResolverConfig::cloudflare(),
49                ResolverOpts::default(),
50            ),
51        })
52    }
53    /// Creates a [`Resolver`] with a custom resolver configuration and options.
54    pub fn custom(cfg: ResolverConfig, opts: ResolverOpts) -> Self {
55        Self {
56            resolver: TokioAsyncResolver::tokio(cfg, opts),
57        }
58    }
59    /// Creates a new [`Resolver`] from the OS's DNS configuration and defaults.
60    pub fn system() -> Result<Self> {
61        let (cfg, opts) = system_conf::read_system_conf()?;
62        Ok(Self::custom(cfg, opts))
63    }
64
65    pub fn from_option(option: ResolverOption) -> Result<Self> {
66        match option {
67            ResolverOption::Default => Self::new(),
68            ResolverOption::System => Self::system(),
69            ResolverOption::Cloudflare => Ok(Self::custom(
70                ResolverConfig::cloudflare(),
71                ResolverOpts::default(),
72            )),
73            ResolverOption::Google => Ok(Self::custom(
74                ResolverConfig::google(),
75                ResolverOpts::default(),
76            )),
77            ResolverOption::Quad9 => Ok(Self::custom(
78                ResolverConfig::quad9(),
79                ResolverOpts::default(),
80            )),
81            ResolverOption::Custom(cfg, opts) => Ok(Self::custom(cfg, opts)),
82        }
83    }
84
85    pub async fn lookup_ip(&self, name: String) -> Result<Vec<IpAddr>, ResolveError> {
86        let lookup: LookupIp = self.resolver.lookup_ip(name).await?;
87        let mut ips = Vec::new();
88        for ip in lookup.into_iter() {
89            ips.push(ip);
90        }
91        Ok(ips)
92    }
93
94    pub async fn ipv4_lookup(&self, name: String) -> Result<Vec<Ipv4Addr>, ResolveError> {
95        let lookup: Ipv4Lookup = self.resolver.ipv4_lookup(name).await?;
96        let mut ips = Vec::new();
97        for ip in lookup.into_iter() {
98            ips.push(ip.0);
99        }
100        Ok(ips)
101    }
102
103    pub async fn ipv6_lookup(&self, name: String) -> Result<Vec<Ipv6Addr>, ResolveError> {
104        let lookup: Ipv6Lookup = self.resolver.ipv6_lookup(name).await?;
105        let mut ips = Vec::new();
106        for ip in lookup.into_iter() {
107            ips.push(ip.0);
108        }
109        Ok(ips)
110    }
111
112    pub async fn txt_lookup(&self, name: String) -> Result<TxtLookup, ResolveError> {
113        self.resolver.txt_lookup(name).await
114    }
115
116    pub async fn resolve(&self, name: String) -> Result<Resolved, ResolveError> {
117        let ips = self.lookup_ip(name.clone()).await?;
118        if ips.len() == 1 {
119            return Ok(Resolved::One(ips[0]));
120        }
121        if ips.len() > 1 {
122            return Ok(Resolved::Many(ips));
123        }
124        let name = [DNS_NODEID_PREFIX, &name].concat();
125        let txts = self.txt_lookup(name).await?;
126        let mut node_ids = Vec::new();
127        for txt in txts {
128            // Parse the TXT record as a `NodeId`. from base32.
129            if let Some(chars) = txt.txt_data().first() {
130                let s = match std::str::from_utf8(chars) {
131                    Ok(s) => s,
132                    Err(_) => continue,
133                };
134                match NodeId::from_base32(s) {
135                    Ok(node_id) => {
136                        node_ids.push(node_id);
137                    }
138                    Err(_) => {
139                        // Ignore invalid `NodeId`s.
140                    }
141                }
142            }
143        }
144        Ok(Resolved::NodeIds(node_ids))
145    }
146
147    pub async fn resolve_addr(&self, addr: &mut StackAddr) -> Result<()> {
148        if addr.resolved() {
149            return Ok(());
150        }
151        let dns = if let Some(dns) = addr.get_dns() {
152            dns
153        } else {
154            return Err(anyhow::anyhow!("Invalid address(No DNS)"));
155        };
156        match dns {
157            Protocol::Dns(name) => {
158                let ips = self.lookup_ip(name.to_string()).await?;
159                for ip in ips {
160                    addr.resolve(ip);
161                    break;
162                }
163            }
164            Protocol::Dns4(name) => {
165                let ips = self.ipv4_lookup(name.to_string()).await?;
166                for ip in ips {
167                    addr.resolve(IpAddr::V4(ip));
168                    break;
169                }
170            }
171            Protocol::Dns6(name) => {
172                let ips = self.ipv6_lookup(name.to_string()).await?;
173                for ip in ips {
174                    addr.resolve(IpAddr::V6(ip));
175                    break;
176                }
177            }
178            _ => {
179                return Err(anyhow::anyhow!("Invalid address. Not supported"));
180            }
181        }
182        if addr.resolved() {
183            return Ok(());
184        } else {
185            return Err(anyhow::anyhow!("Invalid address. Not resolved"));
186        }
187    }
188}