dns_ptr_resolver/
lib.rs

1use hickory_client::client::{Client, SyncClient};
2use hickory_client::op::DnsResponse;
3use hickory_client::rr::{DNSClass, Name, RData, Record, RecordType};
4use hickory_client::tcp::TcpClientConnection;
5use rustdns::util::reverse;
6use std::net::IpAddr;
7use std::str::FromStr;
8use std::{error::Error, fmt};
9
10#[derive(Clone, Debug, PartialEq)]
11/// The result of resolving the pointer or the IP
12pub struct ResolvedResult {
13    /// For example: one.one.one.one.
14    /// For example: dns.google.
15    pub query: Name,
16    /// For example: 1.1.1.1.in-addr.arpa.
17    /// For example: 8.8.8.8.in-addr.arpa.
18    pub result: Option<Name>,
19    pub error: Option<String>,
20}
21
22#[derive(Debug)]
23pub struct ResolvingError {
24    pub message: String,
25}
26
27impl Error for ResolvingError {}
28
29impl fmt::Display for ResolvingError {
30    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31        write!(f, "{}", self.message)
32    }
33}
34
35/**
36 * Resolve a DNS IP adddress (IPv4/IPv6) into a DNS pointer
37 * ```
38 * use hickory_client::client::SyncClient;
39 * use hickory_client::rr::Name;
40 * use hickory_client::tcp::TcpClientConnection;
41 *
42 * use dns_ptr_resolver::{get_ptr, ResolvedResult};
43 *
44 * let server = "1.1.1.1:53".parse().expect("To parse");
45 * let conn = TcpClientConnection::with_timeout(server, std::time::Duration::new(5, 0)).unwrap();
46 * let client = SyncClient::new(conn);
47 * let query_address = "8.8.8.8".parse().expect("To parse");
48 *
49 * assert_eq!(
50 *  get_ptr(query_address, client).unwrap(),
51 *  ResolvedResult {
52 *      query: Name::from_str_relaxed("8.8.8.8.in-addr.arpa.").unwrap(),
53 *      result: Some(Name::from_str_relaxed("dns.google.").unwrap()),
54 *      error: None,
55 *  }
56 * );
57 * ```
58 */
59pub fn get_ptr(
60    ip_address: IpAddr,
61    client: SyncClient<TcpClientConnection>,
62) -> Result<ResolvedResult, ResolvingError> {
63    // Specify the name, note the final '.' which specifies it's an FQDN
64    match Name::from_str(&reverse(ip_address)) {
65        Ok(name) => ptr_resolve(name, client),
66        Err(err) => Err(ResolvingError {
67            message: format!(
68                "Something went wrong while building the name ({}): {}",
69                reverse(ip_address),
70                err
71            ),
72        }),
73    }
74}
75
76/**
77 * This will resolve a name into its DNS pointer value
78 * ```
79 * use hickory_client::client::SyncClient;
80 * use hickory_client::rr::Name;
81 * use hickory_client::tcp::TcpClientConnection;
82 *
83 * use dns_ptr_resolver::{ptr_resolve, ResolvedResult};
84 *
85 * let server = "8.8.8.8:53".parse().expect("To parse");
86 * let conn = TcpClientConnection::with_timeout(server, std::time::Duration::new(5, 0)).unwrap();
87 * let client = SyncClient::new(conn);
88 *
89 * let name_to_resolve = Name::from_str_relaxed("1.1.1.1.in-addr.arpa.").unwrap();
90 *
91 * assert_eq!(
92 *  ptr_resolve(name_to_resolve.clone(), client).unwrap(),
93 *  ResolvedResult {
94 *      query: name_to_resolve,
95 *      result: Some(Name::from_str_relaxed("one.one.one.one.").unwrap()),
96 *      error: None,
97 *  }
98 * );
99 * ```
100 */
101pub fn ptr_resolve(
102    name: Name,
103    client: SyncClient<TcpClientConnection>,
104) -> Result<ResolvedResult, ResolvingError> {
105    let response: DnsResponse = match client.query(&name, DNSClass::IN, RecordType::PTR) {
106        Ok(res) => res,
107        Err(err) => {
108            return Err(ResolvingError {
109                message: format!("Query error for ({}): {}", name, err),
110            })
111        }
112    };
113
114    let answers: &[Record] = response.answers();
115
116    if answers.len() == 0 {
117        return Ok(ResolvedResult {
118            query: name,
119            result: None,
120            error: None,
121        });
122    }
123
124    match answers[0].data() {
125        Some(RData::PTR(res)) => {
126            return Ok(ResolvedResult {
127                query: name,
128                result: Some(res.to_lowercase()),
129                error: None,
130            });
131        }
132        // Example: 87.246.7.75
133        // Replies:
134        // 75.7.246.87.in-addr.arpa. 3600	IN	CNAME	75.0-255.7.246.87.in-addr.arpa.
135        // 75.0-255.7.246.87.in-addr.arpa.	86400 IN PTR	bulbank.linkbg.com.
136        Some(RData::CNAME(res)) => {
137            return ptr_resolve(res.to_lowercase(), client);
138        }
139        Some(res) => {
140            return Err(ResolvingError {
141                message: format!("Unexpected result ({:?}) from: {}", res, name),
142            });
143        }
144        None => {
145            return Err(ResolvingError {
146                message: format!("Weird empty result from: {}", name),
147            });
148        }
149    }
150}
151
152#[cfg(test)]
153mod test {
154    use super::*;
155    use std::process;
156    use std::time::Duration;
157
158    #[test]
159    fn test_get_ptr() {
160        let server = "8.8.8.8:53".parse().expect("To parse");
161        let conn = match TcpClientConnection::with_timeout(server, Duration::new(5, 0)) {
162            Ok(conn) => conn,
163            Err(err) => {
164                eprintln!(
165                    "Something went wrong with the UDP client connection: {}",
166                    err
167                );
168                process::exit(1);
169            }
170        };
171        let client = SyncClient::new(conn);
172
173        let query_address = "8.8.8.8".parse().expect("To parse");
174
175        assert_eq!(
176            get_ptr(query_address, client).unwrap(),
177            ResolvedResult {
178                query: Name::from_str_relaxed("8.8.8.8.in-addr.arpa.").unwrap(),
179                result: Some(Name::from_str_relaxed("dns.google.").unwrap()),
180                error: None,
181            }
182        );
183    }
184
185    #[test]
186    fn test_ptr_resolve() {
187        let server = "1.1.1.1:53".parse().expect("To parse");
188        let conn = match TcpClientConnection::with_timeout(server, Duration::new(5, 0)) {
189            Ok(conn) => conn,
190            Err(err) => {
191                eprintln!(
192                    "Something went wrong with the UDP client connection: {}",
193                    err
194                );
195                process::exit(1);
196            }
197        };
198        let client = SyncClient::new(conn);
199
200        let name_to_resolve = Name::from_str_relaxed("1.1.1.1.in-addr.arpa.").unwrap();
201
202        assert_eq!(
203            ptr_resolve(name_to_resolve.clone(), client).unwrap(),
204            ResolvedResult {
205                query: name_to_resolve,
206                result: Some(Name::from_str_relaxed("one.one.one.one.").unwrap()),
207                error: None,
208            }
209        );
210    }
211
212    #[test]
213    fn test_reverse_dns() {
214        assert_eq!(
215            reverse("192.0.2.12".parse().unwrap()),
216            "12.2.0.192.in-addr.arpa."
217        );
218    }
219}