torrust_tracker_deployer_lib/adapters/network/ss.rs
1//! SS (socket statistics) CLI client implementation
2
3use super::error::NetworkError;
4use crate::shared::command::CommandExecutor;
5
6/// Client for executing ss (socket statistics) commands
7///
8/// This client wraps ss CLI operations using our `CommandExecutor` collaborator,
9/// enabling testability and consistency with other external tool clients.
10///
11/// SS is a modern utility to investigate sockets and is the replacement for netstat.
12/// It's faster and provides more detailed information about network connections.
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::SsClient;
23///
24/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
25/// let ss = SsClient::new();
26///
27/// // List all TCP listening ports with process information
28/// let output = ss.list_tcp_listening_ports()?;
29/// println!("Listening ports:\n{}", output);
30/// # Ok(())
31/// # }
32/// ```
33#[derive(Debug)]
34pub struct SsClient {
35 command_executor: CommandExecutor,
36}
37
38impl Default for SsClient {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl SsClient {
45 /// Create a new ss client
46 ///
47 /// # Example
48 ///
49 /// ```rust
50 /// use torrust_tracker_deployer_lib::adapters::network::SsClient;
51 ///
52 /// let ss = SsClient::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 `ss -tlnp` to list all TCP listening sockets with:
64 /// - `-t`: TCP sockets only
65 /// - `-l`: Listening sockets only
66 /// - `-n`: Numeric addresses (no DNS resolution)
67 /// - `-p`: Show process information (may require root)
68 ///
69 /// # Returns
70 ///
71 /// The ss output as a string
72 ///
73 /// # Errors
74 ///
75 /// Returns `NetworkError::SsFailed` if the command fails or ss is not installed
76 ///
77 /// # Example
78 ///
79 /// ```rust,no_run
80 /// # use torrust_tracker_deployer_lib::adapters::network::SsClient;
81 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
82 /// # let ss = SsClient::new();
83 /// let output = ss.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("ss", &args, None)
99 .map(|result| result.stdout)
100 .map_err(NetworkError::SsFailed)
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn it_should_create_ss_client() {
110 let _client = SsClient::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 = SsClient::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 ss to be installed
121 // and may require root permissions. Integration tests should cover actual execution.
122}