nd_300/diagnostics/
reverse_dns.rs1use serde::Serialize;
2
3use super::shared_cache::SharedCache;
4
5#[derive(Debug, Clone, Serialize)]
6pub struct ReverseDnsEntry {
7 pub ip: String,
8 pub hostname: Option<String>,
9 pub label: String,
10}
11
12pub async fn collect_with_cache(cache: &SharedCache) -> Option<Vec<ReverseDnsEntry>> {
13 let mut entries = Vec::new();
14
15 let gateway_ip = cache.gateway_ip.clone();
16
17 let mut ips_to_check: Vec<(String, String)> = Vec::new();
18
19 if let Some(ref gw) = gateway_ip {
20 ips_to_check.push((gw.clone(), "Gateway".to_string()));
21 }
22
23 ips_to_check.push(("1.1.1.1".to_string(), "Cloudflare DNS".to_string()));
24 ips_to_check.push(("8.8.8.8".to_string(), "Google DNS".to_string()));
25
26 for (ip, label) in ips_to_check {
27 let hostname = reverse_lookup(&ip).await;
28 entries.push(ReverseDnsEntry {
29 ip,
30 hostname,
31 label,
32 });
33 }
34
35 if entries.is_empty() {
36 None
37 } else {
38 Some(entries)
39 }
40}
41
42pub async fn collect() -> Option<Vec<ReverseDnsEntry>> {
43 let mut entries = Vec::new();
44
45 let gateway_ip = default_net::get_default_gateway()
47 .ok()
48 .map(|gw| gw.ip_addr.to_string());
49
50 let mut ips_to_check: Vec<(String, String)> = Vec::new();
52
53 if let Some(ref gw) = gateway_ip {
54 ips_to_check.push((gw.clone(), "Gateway".to_string()));
55 }
56
57 ips_to_check.push(("1.1.1.1".to_string(), "Cloudflare DNS".to_string()));
58 ips_to_check.push(("8.8.8.8".to_string(), "Google DNS".to_string()));
59
60 for (ip, label) in ips_to_check {
61 let hostname = reverse_lookup(&ip).await;
62 entries.push(ReverseDnsEntry {
63 ip,
64 hostname,
65 label,
66 });
67 }
68
69 if entries.is_empty() {
70 None
71 } else {
72 Some(entries)
73 }
74}
75
76async fn reverse_lookup(ip: &str) -> Option<String> {
77 #[cfg(windows)]
78 {
79 let mut cmd = tokio::process::Command::new("nslookup");
80 cmd.args([ip]);
81 let output = super::util::run_with_timeout(cmd, super::util::SLOW).await?;
82
83 let text = String::from_utf8_lossy(&output.stdout);
84 for line in text.lines() {
85 if line.trim().starts_with("Name:") {
86 return line.split(':').nth(1).map(|s| s.trim().to_string());
87 }
88 }
89 None
90 }
91
92 #[cfg(unix)]
93 {
94 let mut cmd = tokio::process::Command::new("dig");
95 cmd.args(["-x", ip, "+short"]);
96 let output = super::util::run_with_timeout(cmd, super::util::SLOW).await?;
97
98 let text = String::from_utf8_lossy(&output.stdout).trim().to_string();
99 if text.is_empty() {
100 None
101 } else {
102 Some(text.trim_end_matches('.').to_string())
103 }
104 }
105}