listeners 0.6.1

Cross-platform library to find out processes listening on network ports
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
#![doc = include_str!("../README.md")]

use std::collections::HashSet;
use std::fmt::Display;
use std::net::SocketAddr;

mod platform;

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

/// Indicates whether the current operating system is supported by this library.
///
/// Currently, the supported operating systems are Windows, Linux, macOS, FreeBSD, OpenBSD and NetBSD.
pub const IS_OS_SUPPORTED: bool = cfg!(any(
    target_os = "windows",
    target_os = "linux",
    target_os = "macos",
    target_os = "freebsd",
    target_os = "openbsd",
    target_os = "netbsd"
));

/// A process listening on a socket.
#[derive(Eq, PartialEq, Hash, Debug, Clone)]
pub struct Listener {
    /// The listening process.
    pub process: Process,
    /// The socket this listener is listening on.
    pub socket: SocketAddr,
    /// The protocol used.
    pub protocol: Protocol,
    /// The state of the socket connection.
    pub state: SocketState,
}

/// An active process.
#[derive(Eq, PartialEq, Hash, Debug, Clone)]
pub struct Process {
    /// Process ID.
    pub pid: u32,
    /// Process name.
    pub name: String,
    /// Process path.
    pub path: String,
}

/// The network protocol used by a socket.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Protocol {
    /// Transmission Control Protocol.
    TCP,
    /// User Datagram Protocol.
    UDP,
}

/// The state of a socket connection.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SocketState {
    /// Connection is open and exchanging data.
    Established,
    /// Initiating a connection.
    SynSent,
    /// Received a connection request.
    SynReceived,
    /// Sent a FIN, waiting for its ACK or the peer's FIN.
    FinWait1,
    /// FIN acknowledged, waiting for the peer's FIN.
    FinWait2,
    /// Waiting for remaining packets after close.
    TimeWait,
    /// Socket is not connected.
    Closed,
    /// Received a FIN, waiting to send FIN.
    CloseWait,
    /// Sent FIN, waiting for ACK.
    LastAck,
    /// Listening for incoming connections.
    Listen,
    /// Both sides sent FIN simultaneously.
    Closing,
    /// State is unknown or not applicable (e.g. UDP).
    Unknown,
}

impl SocketState {
    #[cfg(target_os = "linux")]
    pub(crate) fn from_linux(state_hex: &str) -> Self {
        match u8::from_str_radix(state_hex, 16) {
            Ok(0x01) => Self::Established,
            Ok(0x02) => Self::SynSent,
            Ok(0x03) => Self::SynReceived,
            Ok(0x04) => Self::FinWait1,
            Ok(0x05) => Self::FinWait2,
            Ok(0x06) => Self::TimeWait,
            Ok(0x07) => Self::Closed,
            Ok(0x08) => Self::CloseWait,
            Ok(0x09) => Self::LastAck,
            Ok(0x0A) => Self::Listen,
            Ok(0x0B) => Self::Closing,
            _ => Self::Unknown,
        }
    }

    #[cfg(target_os = "windows")]
    pub(crate) fn from_windows(raw: u32) -> Self {
        match raw {
            1 => Self::Closed,
            2 => Self::Listen,
            3 => Self::SynSent,
            4 => Self::SynReceived,
            5 => Self::Established,
            6 => Self::FinWait1,
            7 => Self::FinWait2,
            8 => Self::CloseWait,
            9 => Self::Closing,
            10 => Self::LastAck,
            11 => Self::TimeWait,
            _ => Self::Unknown,
        }
    }

    #[cfg(any(
        target_os = "macos",
        target_os = "freebsd",
        target_os = "netbsd",
        target_os = "openbsd"
    ))]
    pub(crate) fn from_bsd(raw: i32) -> Self {
        match raw {
            0 => Self::Closed,
            1 => Self::Listen,
            2 => Self::SynSent,
            3 => Self::SynReceived,
            4 => Self::Established,
            5 => Self::CloseWait,
            6 => Self::FinWait1,
            7 => Self::Closing,
            8 => Self::LastAck,
            9 => Self::FinWait2,
            10 => Self::TimeWait,
            _ => Self::Unknown,
        }
    }
}

/// Returns all the [Listener]s.
///
/// # Errors
///
/// This function returns an error if it fails to retrieve listeners for the current platform.
///
/// # Example
///
///  ```
#[doc = include_str!("../examples/get_all.rs")]
/// ```
///
/// Output:
/// ``` text
/// PID: 440     Process name: ControlCenter             Socket: 0.0.0.0:0                      Protocol: UDP     State: UNKNOWN
/// PID: 456     Process name: rapportd                  Socket: [::]:49158                     Protocol: TCP     State: LISTEN
/// PID: 456     Process name: rapportd                  Socket: 0.0.0.0:49158                  Protocol: TCP     State: LISTEN
/// PID: 456     Process name: rapportd                  Socket: 0.0.0.0:0                      Protocol: UDP     State: UNKNOWN
/// PID: 485     Process name: sharingd                  Socket: 0.0.0.0:0                      Protocol: UDP     State: UNKNOWN
/// PID: 516     Process name: WiFiAgent                 Socket: 0.0.0.0:0                      Protocol: UDP     State: UNKNOWN
/// PID: 1480    Process name: rustrover                 Socket: [::7f00:1]:63342               Protocol: TCP     State: ESTABLISHED
/// PID: 2123    Process name: Telegram                  Socket: 192.168.1.102:49659            Protocol: TCP     State: ESTABLISHED
/// PID: 2123    Process name: Telegram                  Socket: 192.168.1.102:49656            Protocol: TCP     State: ESTABLISHED
/// PID: 2156    Process name: Google Chrome             Socket: 0.0.0.0:0                      Protocol: UDP     State: UNKNOWN
/// PID: 2167    Process name: Google Chrome Helper      Socket: 192.168.1.102:60834            Protocol: UDP     State: UNKNOWN
/// PID: 2167    Process name: Google Chrome Helper      Socket: 192.168.1.102:53220            Protocol: UDP     State: UNKNOWN
/// PID: 2167    Process name: Google Chrome Helper      Socket: 192.168.1.102:59216            Protocol: UDP     State: UNKNOWN
/// ```
pub fn get_all() -> Result<HashSet<Listener>> {
    platform::get_all()
}

/// Returns the [Process] listening on a given port.
///
/// # Arguments
///
/// * `port` - The port to look for.
/// * `protocol` - The protocol to look for (TCP or UDP).
///
/// # Errors
///
/// This function returns an error if it fails to retrieve the process listening on the given port, or if no process is found.
///
/// # Example
///
///  ``` no_run
#[doc = include_str!("../examples/get_process_by_port.rs")]
/// ```
///
/// Output:
/// ``` text
/// PID: 2123    Process name: Telegram
/// ```
pub fn get_process_by_port(port: u16, protocol: Protocol) -> Result<Process> {
    if port == 0 {
        return Err("Port can't be 0".into());
    }

    platform::get_process_by_port(port, protocol)
}

impl Listener {
    fn new(
        pid: u32,
        name: String,
        path: String,
        socket: SocketAddr,
        protocol: Protocol,
        state: SocketState,
    ) -> Self {
        let process = Process::new(pid, name, path);
        Self {
            process,
            socket,
            protocol,
            state,
        }
    }
}

impl Process {
    fn new(pid: u32, name: String, path: String) -> Self {
        Self { pid, name, path }
    }
}

impl Display for Listener {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Listener {
            process,
            socket,
            protocol,
            state,
        } = self;
        let process = process.to_string();
        let protocol = protocol.to_string();
        write!(
            f,
            "{process:<52} Socket: {socket:<30} Protocol: {protocol:<7} State: {state}"
        )
    }
}

impl Display for SocketState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SocketState::Established => write!(f, "ESTABLISHED"),
            SocketState::SynSent => write!(f, "SYN_SENT"),
            SocketState::SynReceived => write!(f, "SYN_RECEIVED"),
            SocketState::FinWait1 => write!(f, "FIN_WAIT_1"),
            SocketState::FinWait2 => write!(f, "FIN_WAIT_2"),
            SocketState::TimeWait => write!(f, "TIME_WAIT"),
            SocketState::Closed => write!(f, "CLOSED"),
            SocketState::CloseWait => write!(f, "CLOSE_WAIT"),
            SocketState::LastAck => write!(f, "LAST_ACK"),
            SocketState::Listen => write!(f, "LISTEN"),
            SocketState::Closing => write!(f, "CLOSING"),
            SocketState::Unknown => write!(f, "UNKNOWN"),
        }
    }
}

impl Display for Process {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Process { pid, name, .. } = self;
        write!(f, "PID: {pid:<7} Process name: {name}")
    }
}

impl Display for Protocol {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match *self {
            Protocol::TCP => write!(f, "TCP"),
            Protocol::UDP => write!(f, "UDP"),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};

    use crate::{Listener, Process, Protocol, SocketState};

    #[test]
    fn test_v4_listener_to_string() {
        let listener = Listener::new(
            455,
            "rapportd".to_string(),
            "path/to/rapportd".to_string(),
            SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 51189),
            Protocol::TCP,
            SocketState::Listen,
        );
        assert_eq!(
            listener.to_string(),
            "PID: 455     Process name: rapportd                  Socket: 0.0.0.0:51189                  Protocol: TCP     State: LISTEN"
        );
    }

    #[test]
    fn test_v6_listener_to_string() {
        let listener = Listener::new(
            160,
            "mysqld".to_string(),
            "path/to/mysqld".to_string(),
            SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 3306),
            Protocol::UDP,
            SocketState::Unknown,
        );
        assert_eq!(
            listener.to_string(),
            "PID: 160     Process name: mysqld                    Socket: [::]:3306                      Protocol: UDP     State: UNKNOWN"
        );
    }

    #[test]
    fn test_process_to_string() {
        let process = Process::new(
            611,
            "Microsoft SharePoint".to_string(),
            "path/to/sharepoint".to_string(),
        );
        assert_eq!(
            process.to_string(),
            "PID: 611     Process name: Microsoft SharePoint"
        );
    }

    #[test]
    fn test_socket_state_to_string() {
        assert_eq!(SocketState::Established.to_string(), "ESTABLISHED");
        assert_eq!(SocketState::SynSent.to_string(), "SYN_SENT");
        assert_eq!(SocketState::SynReceived.to_string(), "SYN_RECEIVED");
        assert_eq!(SocketState::FinWait1.to_string(), "FIN_WAIT_1");
        assert_eq!(SocketState::FinWait2.to_string(), "FIN_WAIT_2");
        assert_eq!(SocketState::TimeWait.to_string(), "TIME_WAIT");
        assert_eq!(SocketState::Closed.to_string(), "CLOSED");
        assert_eq!(SocketState::CloseWait.to_string(), "CLOSE_WAIT");
        assert_eq!(SocketState::LastAck.to_string(), "LAST_ACK");
        assert_eq!(SocketState::Listen.to_string(), "LISTEN");
        assert_eq!(SocketState::Closing.to_string(), "CLOSING");
        assert_eq!(SocketState::Unknown.to_string(), "UNKNOWN");
    }

    #[cfg(target_os = "linux")]
    #[test]
    fn test_socket_state_from_linux() {
        assert_eq!(SocketState::from_linux("01"), SocketState::Established);
        assert_eq!(SocketState::from_linux("02"), SocketState::SynSent);
        assert_eq!(SocketState::from_linux("03"), SocketState::SynReceived);
        assert_eq!(SocketState::from_linux("04"), SocketState::FinWait1);
        assert_eq!(SocketState::from_linux("05"), SocketState::FinWait2);
        assert_eq!(SocketState::from_linux("06"), SocketState::TimeWait);
        assert_eq!(SocketState::from_linux("07"), SocketState::Closed);
        assert_eq!(SocketState::from_linux("08"), SocketState::CloseWait);
        assert_eq!(SocketState::from_linux("09"), SocketState::LastAck);
        assert_eq!(SocketState::from_linux("0A"), SocketState::Listen);
        assert_eq!(SocketState::from_linux("0B"), SocketState::Closing);
        // unmapped code, non-hex, and out-of-range values all fall back to Unknown
        assert_eq!(SocketState::from_linux("0C"), SocketState::Unknown);
        assert_eq!(SocketState::from_linux("zz"), SocketState::Unknown);
        assert_eq!(SocketState::from_linux("100"), SocketState::Unknown);
    }

    #[cfg(target_os = "windows")]
    #[test]
    fn test_socket_state_from_windows() {
        assert_eq!(SocketState::from_windows(1), SocketState::Closed);
        assert_eq!(SocketState::from_windows(2), SocketState::Listen);
        assert_eq!(SocketState::from_windows(3), SocketState::SynSent);
        assert_eq!(SocketState::from_windows(4), SocketState::SynReceived);
        assert_eq!(SocketState::from_windows(5), SocketState::Established);
        assert_eq!(SocketState::from_windows(6), SocketState::FinWait1);
        assert_eq!(SocketState::from_windows(7), SocketState::FinWait2);
        assert_eq!(SocketState::from_windows(8), SocketState::CloseWait);
        assert_eq!(SocketState::from_windows(9), SocketState::Closing);
        assert_eq!(SocketState::from_windows(10), SocketState::LastAck);
        assert_eq!(SocketState::from_windows(11), SocketState::TimeWait);
        // 0 and MIB_TCP_STATE_DELETE_TCB (12) and beyond fall back to Unknown
        assert_eq!(SocketState::from_windows(0), SocketState::Unknown);
        assert_eq!(SocketState::from_windows(12), SocketState::Unknown);
    }

    #[cfg(any(
        target_os = "macos",
        target_os = "freebsd",
        target_os = "netbsd",
        target_os = "openbsd"
    ))]
    #[test]
    fn test_socket_state_from_bsd() {
        assert_eq!(SocketState::from_bsd(0), SocketState::Closed);
        assert_eq!(SocketState::from_bsd(1), SocketState::Listen);
        assert_eq!(SocketState::from_bsd(2), SocketState::SynSent);
        assert_eq!(SocketState::from_bsd(3), SocketState::SynReceived);
        assert_eq!(SocketState::from_bsd(4), SocketState::Established);
        assert_eq!(SocketState::from_bsd(5), SocketState::CloseWait);
        assert_eq!(SocketState::from_bsd(6), SocketState::FinWait1);
        assert_eq!(SocketState::from_bsd(7), SocketState::Closing);
        assert_eq!(SocketState::from_bsd(8), SocketState::LastAck);
        assert_eq!(SocketState::from_bsd(9), SocketState::FinWait2);
        assert_eq!(SocketState::from_bsd(10), SocketState::TimeWait);
        // the -1 UDP sentinel and any out-of-range value fall back to Unknown
        assert_eq!(SocketState::from_bsd(-1), SocketState::Unknown);
        assert_eq!(SocketState::from_bsd(11), SocketState::Unknown);
    }
}