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)]
11pub struct ResolvedResult {
13 pub query: Name,
16 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
35pub fn get_ptr(
60 ip_address: IpAddr,
61 client: SyncClient<TcpClientConnection>,
62) -> Result<ResolvedResult, ResolvingError> {
63 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
76pub 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 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}