nd_300/diagnostics/
shared_cache.rs1use std::collections::HashMap;
2
3pub struct SharedCache {
7 pub netstat: Option<NetstatCache>,
8 pub macos_lsof_tcp: Option<Vec<String>>,
12 pub ipconfig: Option<IpconfigCache>,
13 pub sysinfo_networks: Option<sysinfo::Networks>,
14 pub gateway_ip: Option<String>,
15}
16
17pub struct NetstatCache {
19 pub lines: Vec<String>,
20 pub process_map: HashMap<u32, String>,
21}
22
23pub struct IpconfigCache {
25 pub raw: String,
26}
27
28impl SharedCache {
29 pub async fn build_for_tech_mode() -> Self {
32 let (netstat, macos_lsof_tcp, ipconfig, networks, gateway) = tokio::join!(
33 fetch_netstat(),
34 fetch_macos_lsof_tcp_lines(),
35 fetch_ipconfig(),
36 fetch_sysinfo_networks(),
37 fetch_gateway(),
38 );
39
40 Self {
41 netstat,
42 macos_lsof_tcp,
43 ipconfig,
44 sysinfo_networks: networks,
45 gateway_ip: gateway,
46 }
47 }
48}
49
50#[cfg(target_os = "macos")]
55pub(super) async fn fetch_macos_lsof_tcp_lines() -> Option<Vec<String>> {
56 let mut cmd = tokio::process::Command::new("/usr/sbin/lsof");
57 cmd.args(["-nP", "-iTCP", "-FpcPnT"]);
58 let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
59 if !output.status.success() {
60 return None;
61 }
62
63 let lines: Vec<String> = String::from_utf8_lossy(&output.stdout)
64 .lines()
65 .map(str::to_string)
66 .collect();
67 (!lines.is_empty()).then_some(lines)
68}
69
70#[cfg(not(target_os = "macos"))]
71async fn fetch_macos_lsof_tcp_lines() -> Option<Vec<String>> {
72 None
73}
74
75#[cfg(windows)]
78async fn fetch_netstat() -> Option<NetstatCache> {
79 use sysinfo::System;
80
81 let mut cmd = tokio::process::Command::new("netstat");
82 cmd.args(["-ano"]);
83 let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
84
85 let text = String::from_utf8_lossy(&output.stdout);
86 let lines: Vec<String> = text.lines().map(|l| l.to_string()).collect();
87
88 let process_map = tokio::task::spawn_blocking(|| {
90 let mut sys = System::new();
91 sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
92 let mut map = HashMap::new();
93 for (pid, process) in sys.processes() {
94 map.insert(pid.as_u32(), process.name().to_string_lossy().to_string());
95 }
96 map
97 })
98 .await
99 .unwrap_or_default();
100
101 Some(NetstatCache { lines, process_map })
102}
103
104#[cfg(target_os = "macos")]
105async fn fetch_netstat() -> Option<NetstatCache> {
106 let mut cmd = tokio::process::Command::new("netstat");
107 cmd.args(["-anW", "-p", "tcp"]);
111 let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
112
113 let text = String::from_utf8_lossy(&output.stdout);
114 let lines: Vec<String> = text.lines().map(|l| l.to_string()).collect();
115
116 Some(NetstatCache {
117 lines,
118 process_map: HashMap::new(),
119 })
120}
121
122#[cfg(target_os = "linux")]
123async fn fetch_netstat() -> Option<NetstatCache> {
124 let mut cmd = tokio::process::Command::new("netstat");
127 cmd.args(["-an"]);
128 let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
129
130 let text = String::from_utf8_lossy(&output.stdout);
131 let lines: Vec<String> = text.lines().map(|l| l.to_string()).collect();
132
133 Some(NetstatCache {
134 lines,
135 process_map: HashMap::new(),
136 })
137}
138
139#[cfg(windows)]
142async fn fetch_ipconfig() -> Option<IpconfigCache> {
143 let mut cmd = tokio::process::Command::new("ipconfig");
144 cmd.args(["/all"]);
145 let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
146
147 let raw = String::from_utf8_lossy(&output.stdout).to_string();
148 Some(IpconfigCache { raw })
149}
150
151#[cfg(not(windows))]
152async fn fetch_ipconfig() -> Option<IpconfigCache> {
153 None
155}
156
157async fn fetch_sysinfo_networks() -> Option<sysinfo::Networks> {
160 tokio::task::spawn_blocking(|| Some(sysinfo::Networks::new_with_refreshed_list()))
165 .await
166 .unwrap_or(None)
167}
168
169async fn fetch_gateway() -> Option<String> {
172 tokio::task::spawn_blocking(|| {
175 default_net::get_default_gateway()
176 .ok()
177 .map(|gw| gw.ip_addr.to_string())
178 })
179 .await
180 .unwrap_or(None)
181}