nd_300/diagnostics/
routing_table.rs1use serde::Serialize;
2
3#[derive(Debug, Clone, Serialize)]
4pub struct RouteEntry {
5 pub destination: String,
6 pub gateway: String,
7 pub mask: String,
8 pub interface: String,
9 pub metric: Option<u32>,
10 pub flags: Option<String>,
11}
12
13pub async fn collect() -> Option<Vec<RouteEntry>> {
14 #[cfg(windows)]
15 {
16 collect_windows().await
17 }
18
19 #[cfg(target_os = "macos")]
20 {
21 collect_macos().await
22 }
23
24 #[cfg(target_os = "linux")]
25 {
26 collect_linux().await
27 }
28}
29
30#[cfg(windows)]
31async fn collect_windows() -> Option<Vec<RouteEntry>> {
32 let mut cmd = tokio::process::Command::new("route");
33 cmd.args(["print", "-4"]);
34 let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
35
36 let text = String::from_utf8_lossy(&output.stdout);
37 let mut entries = Vec::new();
38 let mut in_routes = false;
39
40 for line in text.lines() {
41 let line = line.trim();
42 if line.starts_with("Network Destination") {
43 in_routes = true;
44 continue;
45 }
46 if line.starts_with("=") || line.is_empty() {
47 if in_routes && !entries.is_empty() {
48 break;
49 }
50 continue;
51 }
52
53 if in_routes {
54 let parts: Vec<&str> = line.split_whitespace().collect();
55 if parts.len() >= 4 {
56 entries.push(RouteEntry {
57 destination: parts[0].to_string(),
58 mask: parts[1].to_string(),
59 gateway: parts[2].to_string(),
60 interface: parts[3].to_string(),
61 metric: parts.get(4).and_then(|s| s.parse().ok()),
62 flags: None,
63 });
64 }
65 }
66 }
67
68 Some(entries)
69}
70
71#[cfg(target_os = "macos")]
72async fn collect_macos() -> Option<Vec<RouteEntry>> {
73 let mut cmd = tokio::process::Command::new("netstat");
74 cmd.args(["-rn", "-f", "inet"]);
75 let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
76
77 let text = String::from_utf8_lossy(&output.stdout);
78 let mut entries = Vec::new();
79
80 for line in text.lines() {
81 if line.starts_with("Destination")
82 || line.starts_with("Routing")
83 || line.starts_with("Internet")
84 {
85 continue;
86 }
87 let parts: Vec<&str> = line.split_whitespace().collect();
88 if parts.len() >= 4 {
89 entries.push(RouteEntry {
90 destination: parts[0].to_string(),
91 gateway: parts[1].to_string(),
92 mask: String::new(),
93 flags: Some(parts[2].to_string()),
94 interface: parts.last().unwrap_or(&"").to_string(),
95 metric: None,
96 });
97 }
98 }
99
100 Some(entries)
101}
102
103#[cfg(target_os = "linux")]
104async fn collect_linux() -> Option<Vec<RouteEntry>> {
105 let mut cmd = tokio::process::Command::new("ip");
106 cmd.args(["route", "show"]);
107 let output = super::util::run_with_timeout(cmd, super::util::QUICK).await?;
108
109 let text = String::from_utf8_lossy(&output.stdout);
110 let mut entries = Vec::new();
111
112 for line in text.lines() {
113 let parts: Vec<&str> = line.split_whitespace().collect();
114 if parts.is_empty() {
115 continue;
116 }
117
118 let dest = parts[0].to_string();
119 let mut gateway = String::new();
120 let mut iface = String::new();
121 let mut metric = None;
122
123 let mut i = 1;
124 while i < parts.len() {
125 match parts[i] {
126 "via" if i + 1 < parts.len() => {
127 gateway = parts[i + 1].to_string();
128 i += 1;
129 }
130 "dev" if i + 1 < parts.len() => {
131 iface = parts[i + 1].to_string();
132 i += 1;
133 }
134 "metric" if i + 1 < parts.len() => {
135 metric = parts[i + 1].parse().ok();
136 i += 1;
137 }
138 _ => {}
139 }
140 i += 1;
141 }
142
143 entries.push(RouteEntry {
144 destination: dest,
145 gateway,
146 mask: String::new(),
147 interface: iface,
148 metric,
149 flags: None,
150 });
151 }
152
153 Some(entries)
154}