Skip to main content

netwatch_rs/
connections.rs

1use std::collections::HashMap;
2use std::fs;
3use std::net::{IpAddr, SocketAddr};
4use std::str::FromStr;
5
6#[derive(Debug, Clone)]
7pub struct NetworkConnection {
8    pub local_addr: SocketAddr,
9    pub remote_addr: SocketAddr,
10    pub state: ConnectionState,
11    pub protocol: Protocol,
12    pub pid: Option<u32>,
13    pub process_name: Option<String>,
14    pub bytes_sent: u64,
15    pub bytes_received: u64,
16    // Enhanced ss command data
17    pub socket_info: SocketInfo,
18}
19
20#[derive(Debug, Clone, Default)]
21pub struct SocketInfo {
22    pub rtt: Option<f64>,          // Round trip time in ms
23    pub rttvar: Option<f64>,       // RTT variation in ms
24    pub cwnd: Option<u32>,         // Congestion window size
25    pub ssthresh: Option<u32>,     // Slow start threshold
26    pub send_queue: u32,           // Send queue size
27    pub recv_queue: u32,           // Receive queue size
28    pub bandwidth: Option<u64>,    // Estimated bandwidth
29    pub pacing_rate: Option<u64>,  // Pacing rate
30    pub retrans: u32,              // Retransmission count
31    pub lost: u32,                 // Lost packet count
32    pub duration: Option<String>,  // Connection duration
33    pub interface: Option<String>, // Network interface
34    pub tcp_info: Option<TcpInfo>, // Extended TCP information
35}
36
37#[derive(Debug, Clone)]
38pub struct TcpInfo {
39    pub mss: u32,                   // Maximum segment size
40    pub pmtu: u32,                  // Path MTU
41    pub rcv_mss: u32,               // Receive MSS
42    pub advmss: u32,                // Advertised MSS
43    pub cwnd_clamp: u32,            // Congestion window clamp
44    pub delivery_rate: Option<u64>, // Delivery rate
45    pub app_limited: bool,          // Application limited
46    pub reordering: u32,            // Packet reordering metric
47}
48
49#[derive(Debug, Clone, PartialEq)]
50pub enum ConnectionState {
51    Established,
52    Listen,
53    SynSent,
54    SynReceived,
55    FinWait1,
56    FinWait2,
57    TimeWait,
58    Close,
59    CloseWait,
60    LastAck,
61    Closing,
62    Unknown,
63}
64
65impl ConnectionState {
66    pub fn as_str(&self) -> &'static str {
67        match self {
68            ConnectionState::Established => "ESTABLISHED",
69            ConnectionState::Listen => "LISTEN",
70            ConnectionState::SynSent => "SYN_SENT",
71            ConnectionState::SynReceived => "SYN_RECV",
72            ConnectionState::FinWait1 => "FIN_WAIT1",
73            ConnectionState::FinWait2 => "FIN_WAIT2",
74            ConnectionState::TimeWait => "TIME_WAIT",
75            ConnectionState::Close => "CLOSE",
76            ConnectionState::CloseWait => "CLOSE_WAIT",
77            ConnectionState::LastAck => "LAST_ACK",
78            ConnectionState::Closing => "CLOSING",
79            ConnectionState::Unknown => "UNKNOWN",
80        }
81    }
82
83    pub fn color(&self) -> ratatui::style::Color {
84        use ratatui::style::Color;
85        match self {
86            ConnectionState::Established => Color::Green,
87            ConnectionState::Listen => Color::Blue,
88            ConnectionState::SynSent | ConnectionState::SynReceived => Color::Yellow,
89            ConnectionState::FinWait1
90            | ConnectionState::FinWait2
91            | ConnectionState::TimeWait
92            | ConnectionState::CloseWait
93            | ConnectionState::LastAck
94            | ConnectionState::Closing => Color::Red,
95            ConnectionState::Close => Color::Gray,
96            ConnectionState::Unknown => Color::Magenta,
97        }
98    }
99}
100
101impl FromStr for ConnectionState {
102    type Err = ();
103
104    fn from_str(s: &str) -> Result<Self, Self::Err> {
105        match s {
106            "01" => Ok(ConnectionState::Established),
107            "02" => Ok(ConnectionState::SynSent),
108            "03" => Ok(ConnectionState::SynReceived),
109            "04" => Ok(ConnectionState::FinWait1),
110            "05" => Ok(ConnectionState::FinWait2),
111            "06" => Ok(ConnectionState::TimeWait),
112            "07" => Ok(ConnectionState::Close),
113            "08" => Ok(ConnectionState::CloseWait),
114            "09" => Ok(ConnectionState::LastAck),
115            "0A" | "10" => Ok(ConnectionState::Listen),
116            "0B" | "11" => Ok(ConnectionState::Closing),
117            _ => Ok(ConnectionState::Unknown),
118        }
119    }
120}
121
122#[derive(Debug, Clone, PartialEq)]
123pub enum Protocol {
124    Tcp,
125    Udp,
126    Tcp6,
127    Udp6,
128}
129
130impl Protocol {
131    pub fn as_str(&self) -> &'static str {
132        match self {
133            Protocol::Tcp => "TCP",
134            Protocol::Udp => "UDP",
135            Protocol::Tcp6 => "TCP6",
136            Protocol::Udp6 => "UDP6",
137        }
138    }
139}
140
141pub struct ConnectionMonitor {
142    connections: Vec<NetworkConnection>,
143    process_cache: HashMap<u32, String>,
144}
145
146impl ConnectionMonitor {
147    pub fn new() -> Self {
148        Self {
149            connections: Vec::new(),
150            process_cache: HashMap::new(),
151        }
152    }
153
154    pub fn update(&mut self) -> Result<(), Box<dyn std::error::Error>> {
155        // Clear existing connections to get fresh data
156        self.connections.clear();
157
158        // On macOS, skip ss command entirely and go straight to netstat/lsof
159        #[cfg(target_os = "macos")]
160        {
161            self.read_tcp_connections()?;
162            self.read_udp_connections()?;
163            // Skip process info update as it may fail on macOS in some environments
164            let _ = self.update_process_info();
165        }
166
167        #[cfg(not(target_os = "macos"))]
168        {
169            // Try using ss command for rich socket information (Linux/modern systems)
170            if self.read_ss_connections().is_ok() {
171                // ss command succeeded, we have rich data
172            } else {
173                // Fallback to /proc parsing or demo data
174                self.read_tcp_connections()?;
175                self.read_udp_connections()?;
176
177                // Update process information
178                self.update_process_info()?;
179            }
180        }
181
182        // Sort by connection quality (RTT first, then bytes transferred)
183        self.connections.sort_by(|a, b| {
184            // First sort by connection health (lower RTT = better)
185            match (a.socket_info.rtt, b.socket_info.rtt) {
186                (Some(rtt_a), Some(rtt_b)) => rtt_a
187                    .partial_cmp(&rtt_b)
188                    .unwrap_or(std::cmp::Ordering::Equal),
189                (Some(_), None) => std::cmp::Ordering::Less,
190                (None, Some(_)) => std::cmp::Ordering::Greater,
191                (None, None) => {
192                    // Fallback to bytes transferred
193                    (b.bytes_sent + b.bytes_received).cmp(&(a.bytes_sent + a.bytes_received))
194                }
195            }
196        });
197
198        Ok(())
199    }
200
201    #[allow(dead_code)]
202    fn read_ss_connections(&mut self) -> Result<(), Box<dyn std::error::Error>> {
203        use std::process::Command;
204
205        // Execute ss command with comprehensive options for rich socket data
206        let output = Command::new("ss")
207            .args(["-tupln", "-i", "-e", "-p"]) // TCP/UDP, processes, listening, numeric, internal, extended
208            .output()?;
209
210        if !output.status.success() {
211            return Err("ss command failed".into());
212        }
213
214        let content = String::from_utf8_lossy(&output.stdout);
215        self.parse_ss_output(&content)?;
216
217        Ok(())
218    }
219
220    #[allow(dead_code)]
221    fn parse_ss_output(&mut self, content: &str) -> Result<(), Box<dyn std::error::Error>> {
222        let lines: Vec<&str> = content.lines().collect();
223        let mut i = 0;
224
225        while i < lines.len() {
226            let line = lines[i].trim();
227
228            // Skip header line
229            if line.starts_with("Netid") || line.is_empty() {
230                i += 1;
231                continue;
232            }
233
234            // Parse main connection line
235            if let Some(connection) = self.parse_ss_connection_line(line)? {
236                // Look for additional lines with socket details
237                let mut socket_info = SocketInfo::default();
238
239                // Check next lines for extended information
240                i += 1;
241                while i < lines.len() {
242                    let next_line = lines[i].trim();
243
244                    // If next line starts with socket details, parse it
245                    if next_line.starts_with("cubic")
246                        || next_line.starts_with("rto:")
247                        || next_line.contains("rtt:")
248                    {
249                        self.parse_socket_details(next_line, &mut socket_info)?;
250                        i += 1;
251                    } else {
252                        // This line doesn't belong to current connection
253                        break;
254                    }
255                }
256
257                let mut conn = connection;
258                conn.socket_info = socket_info;
259                self.connections.push(conn);
260            } else {
261                i += 1;
262            }
263        }
264
265        Ok(())
266    }
267
268    #[allow(dead_code)]
269    fn parse_ss_connection_line(
270        &self,
271        line: &str,
272    ) -> Result<Option<NetworkConnection>, Box<dyn std::error::Error>> {
273        let parts: Vec<&str> = line.split_whitespace().collect();
274        if parts.len() < 5 {
275            return Ok(None);
276        }
277
278        // Parse protocol
279        let protocol = match parts[0] {
280            "tcp" => Protocol::Tcp,
281            "udp" => Protocol::Udp,
282            "tcp6" => Protocol::Tcp6,
283            "udp6" => Protocol::Udp6,
284            _ => return Ok(None),
285        };
286
287        // Parse state
288        let state = match parts[1] {
289            "ESTAB" => ConnectionState::Established,
290            "LISTEN" => ConnectionState::Listen,
291            "SYN-SENT" => ConnectionState::SynSent,
292            "SYN-RECV" => ConnectionState::SynReceived,
293            "FIN-WAIT-1" => ConnectionState::FinWait1,
294            "FIN-WAIT-2" => ConnectionState::FinWait2,
295            "TIME-WAIT" => ConnectionState::TimeWait,
296            "CLOSE" => ConnectionState::Close,
297            "CLOSE-WAIT" => ConnectionState::CloseWait,
298            "LAST-ACK" => ConnectionState::LastAck,
299            "CLOSING" => ConnectionState::Closing,
300            _ => ConnectionState::Unknown,
301        };
302
303        // Parse queue sizes (recv-q send-q)
304        let recv_queue = parts[2].parse().unwrap_or(0);
305        let send_queue = parts[3].parse().unwrap_or(0);
306
307        // Parse local address
308        let local_addr = self.parse_address(parts[4])?;
309
310        // Parse remote address
311        let remote_addr = if parts.len() > 5 && parts[5] != "*:*" {
312            self.parse_address(parts[5])?
313        } else {
314            SocketAddr::new(IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)), 0)
315        };
316
317        // Extract process information if available
318        let (pid, process_name) =
319            if let Some(process_part) = parts.iter().find(|p| p.starts_with("users:")) {
320                self.parse_process_info(process_part)?
321            } else {
322                (None, None)
323            };
324
325        let socket_info = SocketInfo {
326            recv_queue,
327            send_queue,
328            ..Default::default()
329        };
330
331        Ok(Some(NetworkConnection {
332            local_addr,
333            remote_addr,
334            state,
335            protocol,
336            pid,
337            process_name,
338            bytes_sent: 0, // Will be populated from extended info if available
339            bytes_received: 0,
340            socket_info,
341        }))
342    }
343
344    fn parse_address_string(
345        &self,
346        addr_str: &str,
347    ) -> Result<SocketAddr, Box<dyn std::error::Error>> {
348        // Parse macOS netstat format: 192.168.86.21.58412 or [fe80::1]:22
349        if addr_str.contains("*:*") || addr_str == "*" {
350            return Ok(SocketAddr::new(
351                IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)),
352                0,
353            ));
354        }
355
356        if addr_str.starts_with('[') {
357            // IPv6 format: [::1]:22 or [fe80::1]:22
358            let end_bracket = addr_str.find(']').ok_or("Invalid IPv6 format")?;
359            let ip_str = &addr_str[1..end_bracket];
360            let port_str = &addr_str[end_bracket + 2..]; // Skip ']:'
361            let ip = ip_str.parse()?;
362            let port = port_str.parse()?;
363            Ok(SocketAddr::new(ip, port))
364        } else {
365            // IPv4 format: 192.168.86.21.58412 (note: port is after last dot)
366            let last_dot = addr_str.rfind('.').ok_or("Invalid address format")?;
367            let ip_str = &addr_str[..last_dot];
368            let port_str = &addr_str[last_dot + 1..];
369            let ip = ip_str.parse()?;
370            let port = port_str.parse()?;
371            Ok(SocketAddr::new(ip, port))
372        }
373    }
374
375    #[allow(dead_code)]
376    fn parse_address(&self, addr_str: &str) -> Result<SocketAddr, Box<dyn std::error::Error>> {
377        // Handle IPv4 and IPv6 addresses from ss output
378        if addr_str.starts_with('[') {
379            // IPv6 format: [::1]:22
380            let end_bracket = addr_str.find(']').ok_or("Invalid IPv6 format")?;
381            let ip_str = &addr_str[1..end_bracket];
382            let port_str = &addr_str[end_bracket + 2..]; // Skip ']:'
383            let ip = ip_str.parse()?;
384            let port = port_str.parse()?;
385            Ok(SocketAddr::new(ip, port))
386        } else {
387            // IPv4 format: 192.168.1.1:80
388            let parts: Vec<&str> = addr_str.rsplitn(2, ':').collect();
389            if parts.len() != 2 {
390                return Err("Invalid address format".into());
391            }
392            let port = parts[0].parse()?;
393            let ip = parts[1].parse()?;
394            Ok(SocketAddr::new(ip, port))
395        }
396    }
397
398    #[allow(dead_code)]
399    fn parse_process_info(
400        &self,
401        process_part: &str,
402    ) -> Result<(Option<u32>, Option<String>), Box<dyn std::error::Error>> {
403        // Parse format like: users:(("sshd",pid=1234,fd=3))
404        if let Some(start) = process_part.find("pid=") {
405            let pid_part = &process_part[start + 4..];
406            if let Some(end) = pid_part.find(',') {
407                let pid_str = &pid_part[..end];
408                if let Ok(pid) = pid_str.parse::<u32>() {
409                    // Extract process name
410                    if let Some(name_start) = process_part.find("\"") {
411                        if let Some(name_end) = process_part[name_start + 1..].find("\"") {
412                            let name = &process_part[name_start + 1..name_start + 1 + name_end];
413                            return Ok((Some(pid), Some(name.to_string())));
414                        }
415                    }
416                    return Ok((Some(pid), None));
417                }
418            }
419        }
420        Ok((None, None))
421    }
422
423    #[allow(dead_code)]
424    fn parse_socket_details(
425        &self,
426        line: &str,
427        socket_info: &mut SocketInfo,
428    ) -> Result<(), Box<dyn std::error::Error>> {
429        // Parse detailed socket information from ss output
430        for part in line.split_whitespace() {
431            if let Some(rtt_part) = part.strip_prefix("rtt:") {
432                // Parse RTT: rtt:12.5/24.0ms
433                if let Some(slash_pos) = rtt_part.find('/') {
434                    let rtt_str = &rtt_part[..slash_pos];
435                    socket_info.rtt = rtt_str.parse().ok();
436
437                    let rttvar_part = &rtt_part[slash_pos + 1..];
438                    if let Some(ms_pos) = rttvar_part.find("ms") {
439                        let rttvar_str = &rttvar_part[..ms_pos];
440                        socket_info.rttvar = rttvar_str.parse().ok();
441                    }
442                }
443            } else if let Some(cwnd_part) = part.strip_prefix("cwnd:") {
444                socket_info.cwnd = cwnd_part.parse().ok();
445            } else if let Some(ssthresh_part) = part.strip_prefix("ssthresh:") {
446                socket_info.ssthresh = ssthresh_part.parse().ok();
447            } else if part.starts_with("pacing_rate") {
448                // Parse pacing_rate 1.2Mbps
449                if let Some(rate_str) = part.split(':').nth(1) {
450                    socket_info.pacing_rate = self.parse_bandwidth(rate_str);
451                }
452            } else if let Some(retrans_part) = part.strip_prefix("retrans:") {
453                // Parse retrans:0/10
454                if let Some(slash_pos) = retrans_part.find('/') {
455                    socket_info.retrans = retrans_part[..slash_pos].parse().unwrap_or(0);
456                    socket_info.lost = retrans_part[slash_pos + 1..].parse().unwrap_or(0);
457                }
458            }
459        }
460
461        Ok(())
462    }
463
464    #[allow(dead_code)]
465    fn parse_bandwidth(&self, bw_str: &str) -> Option<u64> {
466        let bw_str = bw_str.trim();
467        if let Some(kbps_part) = bw_str.strip_suffix("Kbps") {
468            kbps_part.parse::<f64>().ok().map(|n| (n * 1000.0) as u64)
469        } else if let Some(mbps_part) = bw_str.strip_suffix("Mbps") {
470            mbps_part
471                .parse::<f64>()
472                .ok()
473                .map(|n| (n * 1_000_000.0) as u64)
474        } else if let Some(gbps_part) = bw_str.strip_suffix("Gbps") {
475            gbps_part
476                .parse::<f64>()
477                .ok()
478                .map(|n| (n * 1_000_000_000.0) as u64)
479        } else {
480            bw_str.parse().ok()
481        }
482    }
483
484    fn read_tcp_connections(&mut self) -> Result<(), Box<dyn std::error::Error>> {
485        // Try Linux /proc filesystem first
486        if let Ok(content) = fs::read_to_string("/proc/net/tcp") {
487            self.parse_connections(&content, Protocol::Tcp)?;
488        } else {
489            // macOS - get real connection data from system commands
490            self.create_real_connections_from_system(Protocol::Tcp);
491        }
492
493        if let Ok(content) = fs::read_to_string("/proc/net/tcp6") {
494            self.parse_connections(&content, Protocol::Tcp6)?;
495        } else {
496            // macOS fallback
497            self.create_real_connections_from_system(Protocol::Tcp6);
498        }
499
500        Ok(())
501    }
502
503    fn read_udp_connections(&mut self) -> Result<(), Box<dyn std::error::Error>> {
504        // Read IPv4 UDP connections
505        if let Ok(content) = fs::read_to_string("/proc/net/udp") {
506            self.parse_connections(&content, Protocol::Udp)?;
507        } else {
508            self.create_real_connections_from_system(Protocol::Udp);
509        }
510
511        // Read IPv6 UDP connections
512        if let Ok(content) = fs::read_to_string("/proc/net/udp6") {
513            self.parse_connections(&content, Protocol::Udp6)?;
514        } else {
515            self.create_real_connections_from_system(Protocol::Udp6);
516        }
517
518        Ok(())
519    }
520
521    fn parse_connections(
522        &mut self,
523        content: &str,
524        protocol: Protocol,
525    ) -> Result<(), Box<dyn std::error::Error>> {
526        for line in content.lines().skip(1) {
527            // Skip header
528            let fields: Vec<&str> = line.split_whitespace().collect();
529            if fields.len() < 10 {
530                continue;
531            }
532
533            // Parse local and remote addresses
534            let local_addr = self.parse_socket_addr(fields[1])?;
535            let remote_addr = self.parse_socket_addr(fields[2])?;
536
537            // Parse connection state
538            let state = ConnectionState::from_str(fields[3]).unwrap_or(ConnectionState::Unknown);
539
540            // Parse PID (if available in field 7)
541            let pid = if fields.len() > 7 {
542                fields[7].parse().ok()
543            } else {
544                None
545            };
546
547            // Create connection
548            let connection = NetworkConnection {
549                local_addr,
550                remote_addr,
551                state,
552                protocol: protocol.clone(),
553                pid,
554                process_name: None, // Will be filled later
555                bytes_sent: 0,      // Would need additional parsing from /proc/net/netstat
556                bytes_received: 0,
557                socket_info: SocketInfo::default(),
558            };
559
560            self.connections.push(connection);
561        }
562
563        Ok(())
564    }
565
566    fn parse_socket_addr(&self, addr_str: &str) -> Result<SocketAddr, Box<dyn std::error::Error>> {
567        let parts: Vec<&str> = addr_str.split(':').collect();
568        if parts.len() != 2 {
569            return Err("Invalid socket address format".into());
570        }
571
572        // Parse IP address (hex format)
573        let ip_hex = parts[0];
574        let port_hex = parts[1];
575
576        let port = u16::from_str_radix(port_hex, 16)?;
577
578        // Parse IP address based on length
579        let ip = if ip_hex.len() == 8 {
580            // IPv4 address in hex (little-endian)
581            let ip_num = u32::from_str_radix(ip_hex, 16)?;
582            let ip_bytes = [
583                (ip_num & 0xFF) as u8,
584                ((ip_num >> 8) & 0xFF) as u8,
585                ((ip_num >> 16) & 0xFF) as u8,
586                ((ip_num >> 24) & 0xFF) as u8,
587            ];
588            IpAddr::V4(ip_bytes.into())
589        } else if ip_hex.len() == 32 {
590            // IPv6 address in hex
591            let mut ip_bytes = [0u8; 16];
592            for i in 0..16 {
593                ip_bytes[i] = u8::from_str_radix(&ip_hex[i * 2..i * 2 + 2], 16)?;
594            }
595            IpAddr::V6(ip_bytes.into())
596        } else {
597            return Err("Invalid IP address length".into());
598        };
599
600        Ok(SocketAddr::new(ip, port))
601    }
602
603    fn update_process_info(&mut self) -> Result<(), Box<dyn std::error::Error>> {
604        // Build PID to process name mapping
605        let mut pid_to_name = HashMap::new();
606
607        if let Ok(entries) = fs::read_dir("/proc") {
608            for entry in entries.flatten() {
609                if let Ok(file_name) = entry.file_name().into_string() {
610                    if let Ok(pid) = file_name.parse::<u32>() {
611                        let comm_path = format!("/proc/{pid}/comm");
612                        if let Ok(process_name) = fs::read_to_string(comm_path) {
613                            pid_to_name.insert(pid, process_name.trim().to_string());
614                        }
615                    }
616                }
617            }
618        }
619
620        // Update connection process names
621        for connection in &mut self.connections {
622            if let Some(pid) = connection.pid {
623                connection.process_name = pid_to_name.get(&pid).cloned();
624            }
625        }
626
627        self.process_cache = pid_to_name;
628        Ok(())
629    }
630
631    pub fn get_connections(&self) -> &[NetworkConnection] {
632        &self.connections
633    }
634
635    pub fn get_connection_stats(&self) -> ConnectionStats {
636        let mut stats = ConnectionStats::default();
637
638        for conn in &self.connections {
639            match conn.state {
640                ConnectionState::Established => stats.established += 1,
641                ConnectionState::Listen => stats.listening += 1,
642                ConnectionState::TimeWait => stats.time_wait += 1,
643                _ => stats.other += 1,
644            }
645
646            match conn.protocol {
647                Protocol::Tcp | Protocol::Tcp6 => stats.tcp += 1,
648                Protocol::Udp | Protocol::Udp6 => stats.udp += 1,
649            }
650
651            stats.total += 1;
652        }
653
654        stats
655    }
656
657    pub fn get_top_processes(&self) -> Vec<(String, u32)> {
658        let mut process_counts: HashMap<String, u32> = HashMap::new();
659
660        for conn in &self.connections {
661            if let Some(process_name) = &conn.process_name {
662                *process_counts.entry(process_name.clone()).or_insert(0) += 1;
663            }
664        }
665
666        let mut sorted_processes: Vec<(String, u32)> = process_counts.into_iter().collect();
667        sorted_processes.sort_by(|a, b| b.1.cmp(&a.1));
668        sorted_processes.truncate(10); // Top 10
669
670        sorted_processes
671    }
672
673    pub fn get_remote_hosts(&self) -> Vec<(IpAddr, u32)> {
674        let mut host_counts: HashMap<IpAddr, u32> = HashMap::new();
675
676        for conn in &self.connections {
677            if conn.state == ConnectionState::Established {
678                *host_counts.entry(conn.remote_addr.ip()).or_insert(0) += 1;
679            }
680        }
681
682        let mut sorted_hosts: Vec<(IpAddr, u32)> = host_counts.into_iter().collect();
683        sorted_hosts.sort_by(|a, b| b.1.cmp(&a.1));
684        sorted_hosts.truncate(10); // Top 10
685
686        sorted_hosts
687    }
688}
689
690#[derive(Default)]
691pub struct ConnectionStats {
692    pub total: u32,
693    pub established: u32,
694    pub listening: u32,
695    pub time_wait: u32,
696    pub other: u32,
697    pub tcp: u32,
698    pub udp: u32,
699}
700
701impl ConnectionMonitor {
702    fn create_real_connections_from_system(&mut self, protocol: Protocol) {
703        // Use system commands to get real connection data instead of fake demo data
704        self.get_connections_from_netstat(protocol);
705    }
706
707    fn get_connections_from_netstat(&mut self, protocol: Protocol) {
708        use std::process::Command;
709
710        let protocol_flag = match protocol {
711            Protocol::Tcp => "tcp",
712            Protocol::Tcp6 => "tcp6",
713            Protocol::Udp => "udp",
714            Protocol::Udp6 => "udp6",
715        };
716
717        // Use netstat to get real connection data
718        let output = Command::new("netstat")
719            .args(["-n", "-p", protocol_flag])
720            .output();
721
722        match output {
723            Ok(output) => {
724                let stdout = String::from_utf8_lossy(&output.stdout);
725                self.parse_netstat_output(&stdout, protocol);
726            }
727            Err(_e) => {
728                // If netstat fails, try lsof as fallback
729                self.get_connections_from_lsof(protocol);
730            }
731        }
732    }
733
734    fn get_connections_from_lsof(&mut self, protocol: Protocol) {
735        use std::process::Command;
736
737        let protocol_flag = match protocol {
738            Protocol::Tcp | Protocol::Tcp6 => "TCP",
739            Protocol::Udp | Protocol::Udp6 => "UDP",
740        };
741
742        let output = Command::new("lsof")
743            .args(["-i", protocol_flag, "-n"])
744            .output();
745
746        if let Ok(output) = output {
747            let stdout = String::from_utf8_lossy(&output.stdout);
748            self.parse_lsof_output(&stdout, protocol);
749        }
750        // If both netstat and lsof fail, just leave connections empty instead of fake data
751    }
752
753    fn parse_netstat_output(&mut self, output: &str, protocol: Protocol) {
754        // Parse real netstat output to create NetworkConnection objects
755        for line in output.lines().skip(2) {
756            // Skip headers
757            if let Some(connection) = self.parse_netstat_line(line, &protocol) {
758                self.connections.push(connection);
759            }
760        }
761    }
762
763    fn parse_lsof_output(&mut self, output: &str, protocol: Protocol) {
764        // Parse real lsof output to create NetworkConnection objects
765        for line in output.lines().skip(1) {
766            // Skip header
767            if let Some(connection) = self.parse_lsof_line(line, &protocol) {
768                self.connections.push(connection);
769            }
770        }
771    }
772
773    fn parse_netstat_line(&self, line: &str, protocol: &Protocol) -> Option<NetworkConnection> {
774        // Parse macOS netstat format: tcp4  0  0  192.168.86.21.58412  34.36.57.103.443  ESTABLISHED
775        let parts: Vec<&str> = line.split_whitespace().collect();
776        if parts.len() < 6 {
777            return None;
778        }
779
780        // Skip header lines
781        if parts[0] == "Proto" || parts[0] == "Active" {
782            return None;
783        }
784
785        // Parse local and remote addresses (parts[3] and parts[4])
786        let local_addr = self.parse_address_string(parts[3]).ok()?;
787        let remote_addr = self.parse_address_string(parts[4]).unwrap_or_else(|_| {
788            std::net::SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)), 0)
789        });
790
791        // Parse state (parts[5])
792        let state = match parts[5] {
793            "ESTABLISHED" => ConnectionState::Established,
794            "LISTEN" => ConnectionState::Listen,
795            "TIME_WAIT" => ConnectionState::TimeWait,
796            "CLOSE_WAIT" => ConnectionState::CloseWait,
797            "FIN_WAIT_1" => ConnectionState::FinWait1,
798            "FIN_WAIT_2" => ConnectionState::FinWait2,
799            "SYN_SENT" => ConnectionState::SynSent,
800            "SYN_RECV" => ConnectionState::SynReceived,
801            "CLOSING" => ConnectionState::Closing,
802            "LAST_ACK" => ConnectionState::LastAck,
803            _ => ConnectionState::Unknown,
804        };
805
806        Some(NetworkConnection {
807            local_addr,
808            remote_addr,
809            state,
810            protocol: protocol.clone(),
811            pid: None,
812            process_name: None,
813            bytes_sent: 0,
814            bytes_received: 0,
815            socket_info: SocketInfo::default(),
816        })
817    }
818
819    fn parse_lsof_line(&self, line: &str, protocol: &Protocol) -> Option<NetworkConnection> {
820        // Parse macOS lsof format: command pid user fd type device size/off node name
821        // Example: rapportd 699 kevin 8u IPv4 0x666a2de494291f52 0t0 TCP *:64566 (LISTEN)
822        // Example: identitys 721 kevin 36u IPv6 0xe327a3d736b97a9a 0t0 TCP [fe80:18::3af0:34ed:86c8:f8bc]:1024->[fe80:18::190d:c0da:7b3c:37fa]:1024 (ESTABLISHED)
823        let parts: Vec<&str> = line.split_whitespace().collect();
824        if parts.len() < 8 {
825            return None;
826        }
827
828        // Skip header line
829        if parts[0] == "COMMAND" {
830            return None;
831        }
832
833        let process_name = Some(parts[0].to_string());
834        let pid = parts[1].parse::<u32>().ok();
835
836        // Find the TCP/UDP part and connection info (usually last few parts)
837        let network_part = parts
838            .iter()
839            .find(|&&part| part.contains("->") || (part.contains(":") && !part.contains("0x")))?;
840
841        // Parse connection state from the last part in parentheses
842        let state = if let Some(state_part) = parts.last() {
843            match state_part.trim_matches(|c| c == '(' || c == ')') {
844                "ESTABLISHED" => ConnectionState::Established,
845                "LISTEN" => ConnectionState::Listen,
846                "TIME_WAIT" => ConnectionState::TimeWait,
847                "CLOSE_WAIT" => ConnectionState::CloseWait,
848                "SYN_SENT" => ConnectionState::SynSent,
849                "SYN_RECV" => ConnectionState::SynReceived,
850                "FIN_WAIT1" => ConnectionState::FinWait1,
851                "FIN_WAIT2" => ConnectionState::FinWait2,
852                "CLOSING" => ConnectionState::Closing,
853                "LAST_ACK" => ConnectionState::LastAck,
854                _ => ConnectionState::Unknown,
855            }
856        } else {
857            ConnectionState::Unknown
858        };
859
860        // Parse addresses based on format
861        if let Some((local_str, remote_str)) = network_part.split_once("->") {
862            // Established connection with local->remote
863            let local_addr = self.parse_lsof_address(local_str).ok()?;
864            let remote_addr = self.parse_lsof_address(remote_str).ok()?;
865
866            return Some(NetworkConnection {
867                local_addr,
868                remote_addr,
869                state,
870                protocol: protocol.clone(),
871                pid,
872                process_name,
873                bytes_sent: 0,
874                bytes_received: 0,
875                socket_info: SocketInfo::default(),
876            });
877        } else if network_part.contains(":") {
878            // Listening socket (format: *:port or ip:port)
879            let local_addr = self.parse_lsof_address(network_part).ok()?;
880            let remote_addr = std::net::SocketAddr::new(
881                std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)),
882                0,
883            );
884
885            return Some(NetworkConnection {
886                local_addr,
887                remote_addr,
888                state,
889                protocol: protocol.clone(),
890                pid,
891                process_name,
892                bytes_sent: 0,
893                bytes_received: 0,
894                socket_info: SocketInfo::default(),
895            });
896        }
897
898        None
899    }
900
901    fn parse_lsof_address(&self, addr_str: &str) -> Result<SocketAddr, Box<dyn std::error::Error>> {
902        // Parse lsof address formats:
903        // *:64566 (listening on all interfaces)
904        // [fe80:18::3af0:34ed:86c8:f8bc]:1024 (IPv6)
905        // 192.168.1.1:80 (IPv4)
906
907        if let Some(port_str) = addr_str.strip_prefix("*:") {
908            // Wildcard listening address
909            let port = port_str.parse()?;
910            return Ok(SocketAddr::new(
911                IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)),
912                port,
913            ));
914        }
915
916        if addr_str.starts_with('[') {
917            // IPv6 format: [fe80::1]:22
918            let end_bracket = addr_str.find(']').ok_or("Invalid IPv6 format")?;
919            let ip_str = &addr_str[1..end_bracket];
920            let port_str = &addr_str[end_bracket + 2..]; // Skip ']:'
921            let ip = ip_str.parse()?;
922            let port = port_str.parse()?;
923            Ok(SocketAddr::new(ip, port))
924        } else {
925            // IPv4 format: 192.168.1.1:80
926            let parts: Vec<&str> = addr_str.rsplitn(2, ':').collect();
927            if parts.len() != 2 {
928                return Err("Invalid address format".into());
929            }
930            let port = parts[0].parse()?;
931            let ip = parts[1].parse()?;
932            Ok(SocketAddr::new(ip, port))
933        }
934    }
935}
936
937impl Default for ConnectionMonitor {
938    fn default() -> Self {
939        Self::new()
940    }
941}