nd_300/diagnostics/
dns_cache.rs1use serde::Serialize;
2
3#[derive(Debug, Clone, Serialize)]
4pub struct DnsCacheEntry {
5 pub name: String,
6 pub record_type: String,
7 pub data: String,
8 pub ttl: Option<u32>,
9}
10
11pub async fn collect() -> Option<Vec<DnsCacheEntry>> {
12 #[cfg(windows)]
13 {
14 collect_windows().await
15 }
16
17 #[cfg(target_os = "macos")]
18 {
19 None
21 }
22
23 #[cfg(target_os = "linux")]
24 {
25 collect_linux().await
26 }
27}
28
29#[cfg(windows)]
30async fn collect_windows() -> Option<Vec<DnsCacheEntry>> {
31 let mut cmd = tokio::process::Command::new("ipconfig");
32 cmd.args(["/displaydns"]);
33 let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
34
35 let text = String::from_utf8_lossy(&output.stdout);
36 let mut entries = Vec::new();
37 let mut current_name = String::new();
38 let mut current_type = String::new();
39 let mut current_ttl = None;
40
41 for line in text.lines() {
42 let line = line.trim();
43
44 if line.contains("Record Name") {
45 current_name = line
46 .split(':')
47 .nth(1)
48 .map(|s| s.trim().to_string())
49 .unwrap_or_default();
50 } else if line.contains("Record Type") {
51 let type_num: u32 = line
52 .split(':')
53 .nth(1)
54 .and_then(|s| s.trim().parse().ok())
55 .unwrap_or(0);
56 current_type = match type_num {
57 1 => "A",
58 5 => "CNAME",
59 28 => "AAAA",
60 12 => "PTR",
61 15 => "MX",
62 _ => "OTHER",
63 }
64 .to_string();
65 } else if line.contains("Time To Live") {
66 current_ttl = line.split(':').nth(1).and_then(|s| s.trim().parse().ok());
67 } else if line.contains("A (Host) Record")
68 || line.contains("CNAME Record")
69 || line.contains("AAAA Record")
70 {
71 let data = line
72 .split_once(':')
73 .map(|x| x.1)
74 .unwrap_or("")
75 .trim()
76 .to_string();
77 if !current_name.is_empty() {
78 entries.push(DnsCacheEntry {
79 name: current_name.clone(),
80 record_type: current_type.clone(),
81 data,
82 ttl: current_ttl,
83 });
84 }
85 }
86 }
87
88 entries.truncate(50);
90
91 if entries.is_empty() {
92 None
93 } else {
94 Some(entries)
95 }
96}
97
98#[cfg(target_os = "linux")]
99async fn collect_linux() -> Option<Vec<DnsCacheEntry>> {
100 let mut cmd = tokio::process::Command::new("resolvectl");
102 cmd.args(["statistics"]);
103 if let Some(output) = super::util::run_with_timeout(cmd, super::util::SLOW).await {
104 let text = String::from_utf8_lossy(&output.stdout);
105 if !text.is_empty() {
106 let mut entries = Vec::new();
109 for line in text.lines() {
110 if line.contains("Current Cache Size") {
111 entries.push(DnsCacheEntry {
112 name: "Cache Statistics".to_string(),
113 record_type: "INFO".to_string(),
114 data: line.trim().to_string(),
115 ttl: None,
116 });
117 }
118 }
119 if !entries.is_empty() {
120 return Some(entries);
121 }
122 }
123 }
124
125 None
126}