#![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>>;
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"
));
#[derive(Eq, PartialEq, Hash, Debug, Clone)]
pub struct Listener {
pub process: Process,
pub socket: SocketAddr,
pub protocol: Protocol,
pub state: SocketState,
}
#[derive(Eq, PartialEq, Hash, Debug, Clone)]
pub struct Process {
pub pid: u32,
pub name: String,
pub path: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Protocol {
TCP,
UDP,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SocketState {
Established,
SynSent,
SynReceived,
FinWait1,
FinWait2,
TimeWait,
Closed,
CloseWait,
LastAck,
Listen,
Closing,
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,
}
}
}
#[doc = include_str!("../examples/get_all.rs")]
pub fn get_all() -> Result<HashSet<Listener>> {
platform::get_all()
}
#[doc = include_str!("../examples/get_process_by_port.rs")]
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"
);
}
#[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);
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);
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);
assert_eq!(SocketState::from_bsd(-1), SocketState::Unknown);
assert_eq!(SocketState::from_bsd(11), SocketState::Unknown);
}
}