Skip to main content

torrust_tracker_deployer_lib/adapters/network/
netstat.rs

1//! Netstat CLI client implementation
2
3use super::error::NetworkError;
4use crate::shared::command::CommandExecutor;
5
6/// Client for executing netstat commands
7///
8/// This client wraps netstat CLI operations using our `CommandExecutor` collaborator,
9/// enabling testability and consistency with other external tool clients.
10///
11/// Netstat is a command-line tool that displays network connections, routing tables,
12/// interface statistics, masquerade connections, and multicast memberships.
13///
14/// # Architecture
15///
16/// The client uses `CommandExecutor` as a collaborator for actual command execution,
17/// following the same pattern as other adapters in this crate.
18///
19/// # Example
20///
21/// ```rust,no_run
22/// use torrust_tracker_deployer_lib::adapters::network::NetstatClient;
23///
24/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
25/// let netstat = NetstatClient::new();
26///
27/// // List all TCP listening ports with process information
28/// let output = netstat.list_tcp_listening_ports()?;
29/// println!("Listening ports:\n{}", output);
30/// # Ok(())
31/// # }
32/// ```
33#[derive(Debug)]
34pub struct NetstatClient {
35    command_executor: CommandExecutor,
36}
37
38impl Default for NetstatClient {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl NetstatClient {
45    /// Create a new netstat client
46    ///
47    /// # Example
48    ///
49    /// ```rust
50    /// use torrust_tracker_deployer_lib::adapters::network::NetstatClient;
51    ///
52    /// let netstat = NetstatClient::new();
53    /// ```
54    #[must_use]
55    pub fn new() -> Self {
56        Self {
57            command_executor: CommandExecutor::new(),
58        }
59    }
60
61    /// List TCP listening ports with process information
62    ///
63    /// Executes `netstat -tlnp` to list all TCP listening sockets with:
64    /// - `-t`: TCP connections only
65    /// - `-l`: Listening sockets only
66    /// - `-n`: Numeric addresses (no DNS resolution)
67    /// - `-p`: Show process ID and name (may require root)
68    ///
69    /// # Returns
70    ///
71    /// The netstat output as a string
72    ///
73    /// # Errors
74    ///
75    /// Returns `NetworkError::NetstatFailed` if the command fails or netstat is not installed
76    ///
77    /// # Example
78    ///
79    /// ```rust,no_run
80    /// # use torrust_tracker_deployer_lib::adapters::network::NetstatClient;
81    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
82    /// # let netstat = NetstatClient::new();
83    /// let output = netstat.list_tcp_listening_ports()?;
84    ///
85    /// // Parse output to find specific ports
86    /// for line in output.lines() {
87    ///     if line.contains(":8080") {
88    ///         println!("Port 8080 is in use: {}", line);
89    ///     }
90    /// }
91    /// # Ok(())
92    /// # }
93    /// ```
94    pub fn list_tcp_listening_ports(&self) -> Result<String, NetworkError> {
95        let args = vec!["-tlnp"];
96
97        self.command_executor
98            .run_command("netstat", &args, None)
99            .map(|result| result.stdout)
100            .map_err(NetworkError::NetstatFailed)
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn it_should_create_netstat_client() {
110        let _client = NetstatClient::new();
111        // Successfully creating the client is sufficient for this test
112    }
113
114    #[test]
115    fn it_should_have_default_implementation() {
116        let _client = NetstatClient::default();
117        // Successfully creating the client is sufficient for this test
118    }
119
120    // Note: We don't test actual command execution here as it requires netstat to be installed
121    // and may require root permissions. Integration tests should cover actual execution.
122}