From ab1e00354f1d15528b2917769c3c9f4f4b0e4ac8 Mon Sep 17 00:00:00 2001 From: Anton Liashkevich Date: Fri, 8 May 2026 22:50:56 -0400 Subject: [PATCH 1/8] Add SocketState field to Listener Introduces a `SocketState` enum covering all standard TCP connection states (Established, Listen, SynSent, SynReceived, FinWait1, FinWait2, TimeWait, Closed, CloseWait, LastAck, Closing), plus Unknown for UDP or unrecognized raw values. Each platform decodes the kernel's raw state integer into SocketState: - Linux: parses the hex state column from /proc/net/tcp and /proc/net/tcp6 - Windows: maps MIB_TCP_STATE integer constants - macOS/BSD: maps BSD kernel socket state integers The `state` field is added to the public `Listener` struct and included in its `Display` output. Integration tests cover Listen and Established states for both IPv4 and IPv6, CloseWait (verified by reading until EOF on the accepted socket before sampling), and UDP state. --- src/lib.rs | 135 ++++++++++++++++++++++++- src/platform/bsd/ffi/mod.rs | 5 +- src/platform/bsd/freebsd.rs | 1 + src/platform/bsd/native/common.h | 1 + src/platform/bsd/native/freebsd.c | 2 + src/platform/bsd/native/netbsd.c | 1 + src/platform/bsd/native/openbsd.c | 1 + src/platform/bsd/netbsd.rs | 1 + src/platform/bsd/openbsd.rs | 1 + src/platform/linux/mod.rs | 1 + src/platform/linux/proto_listener.rs | 17 +++- src/platform/macos/c_socket_fd_info.rs | 15 ++- src/platform/macos/mod.rs | 1 + src/platform/macos/proto_listener.rs | 10 +- src/platform/windows/proto_listener.rs | 20 +++- src/platform/windows/socket_table.rs | 5 + tests/integration.rs | 122 +++++++++++++++++++++- 17 files changed, 318 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b622979..0e07dfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,8 @@ pub struct Listener { pub socket: SocketAddr, /// The protocol used. pub protocol: Protocol, + /// The state of the socket connection. + pub state: SocketState, } /// An active process. @@ -51,6 +53,96 @@ pub enum 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, + /// First step of the four-way closing handshake. + FinWait1, + /// Second step of the four-way closing handshake. + 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(raw: u8) -> Self { + match raw { + 0x01 => Self::Established, + 0x02 => Self::SynSent, + 0x03 => Self::SynReceived, + 0x04 => Self::FinWait1, + 0x05 => Self::FinWait2, + 0x06 => Self::TimeWait, + 0x07 => Self::Closed, + 0x08 => Self::CloseWait, + 0x09 => Self::LastAck, + 0x0A => Self::Listen, + 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 @@ -113,12 +205,20 @@ pub fn get_process_by_port(port: u16, protocol: Protocol) -> Result { } impl Listener { - fn new(pid: u32, name: String, path: String, socket: SocketAddr, protocol: Protocol) -> Self { + 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, } } } @@ -135,9 +235,32 @@ impl Display for Listener { process, socket, protocol, + state, } = self; let process = process.to_string(); - write!(f, "{process:<55} Socket: {socket:<30} Protocol: {protocol}",) + write!( + f, + "{process:<55} Socket: {socket:<30} Protocol: {protocol} 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_WAIT1"), + SocketState::FinWait2 => write!(f, "FIN_WAIT2"), + 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"), + } } } @@ -161,7 +284,7 @@ impl Display for Protocol { mod tests { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; - use crate::{Listener, Process, Protocol}; + use crate::{Listener, Process, Protocol, SocketState}; #[test] fn test_v4_listener_to_string() { @@ -171,10 +294,11 @@ mod tests { "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" + "PID: 455 Process name: rapportd Socket: 0.0.0.0:51189 Protocol: TCP State: LISTEN" ); } @@ -186,10 +310,11 @@ mod tests { "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" + "PID: 160 Process name: mysqld Socket: [::]:3306 Protocol: UDP State: UNKNOWN" ); } diff --git a/src/platform/bsd/ffi/mod.rs b/src/platform/bsd/ffi/mod.rs index 351338f..d32fc9f 100644 --- a/src/platform/bsd/ffi/mod.rs +++ b/src/platform/bsd/ffi/mod.rs @@ -1,6 +1,6 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; -use crate::Protocol; +use crate::{Protocol, SocketState}; #[cfg(target_os = "freebsd")] type KvAddr = usize; @@ -18,6 +18,7 @@ pub(super) mod openbsd; pub(super) struct SocketInfo { pub(super) address: SocketAddr, pub(super) protocol: Protocol, + pub(super) state: SocketState, #[cfg(any(target_os = "freebsd", target_os = "netbsd"))] pub(super) kvaddr: KvAddr, } @@ -41,6 +42,7 @@ pub(super) struct CSocketInfo { kvaddr: KvAddr, protocol: i32, port: u16, + state: i32, } impl From<&CSocketInfo> for SocketInfo { @@ -62,6 +64,7 @@ impl From<&CSocketInfo> for SocketInfo { libc::IPPROTO_TCP => Protocol::TCP, _ => Protocol::UDP, }, + state: SocketState::from_bsd(value.state), } } } diff --git a/src/platform/bsd/freebsd.rs b/src/platform/bsd/freebsd.rs index 2e335db..eb609e2 100644 --- a/src/platform/bsd/freebsd.rs +++ b/src/platform/bsd/freebsd.rs @@ -24,6 +24,7 @@ pub(crate) fn get_all() -> crate::Result> { path, socket.address, socket.protocol, + socket.state, )); } } diff --git a/src/platform/bsd/native/common.h b/src/platform/bsd/native/common.h index 58d23b1..d00e3e2 100644 --- a/src/platform/bsd/native/common.h +++ b/src/platform/bsd/native/common.h @@ -53,6 +53,7 @@ extern "C" #endif int32_t protocol; uint16_t port; + int32_t state; }; #if defined(__FreeBSD__) || defined(__NetBSD__) diff --git a/src/platform/bsd/native/freebsd.c b/src/platform/bsd/native/freebsd.c index b281298..56f2c86 100644 --- a/src/platform/bsd/native/freebsd.c +++ b/src/platform/bsd/native/freebsd.c @@ -39,6 +39,7 @@ static void fillsock_tcp(struct socket_info_t *sock, struct xtcpcb *xtp) sock->port = ntohs(xtp->xt_inp.inp_lport); sock->protocol = IPPROTO_TCP; sock->kvaddr = xtp->xt_inp.xi_socket.xso_so; + sock->state = xtp->t_state; if (xtp->xt_inp.inp_vflag & INP_IPV6) { @@ -66,6 +67,7 @@ static void fillsock_udp(struct socket_info_t *sock, struct xinpcb *xip) sock->port = ntohs(xip->inp_lport); sock->protocol = IPPROTO_UDP; sock->kvaddr = xip->xi_socket.xso_so; + sock->state = -1; if (xip->inp_vflag & INP_IPV6) { diff --git a/src/platform/bsd/native/netbsd.c b/src/platform/bsd/native/netbsd.c index 49c0b80..68030a1 100644 --- a/src/platform/bsd/native/netbsd.c +++ b/src/platform/bsd/native/netbsd.c @@ -111,6 +111,7 @@ static int fetch_sockets_common(struct socket_info_t **list, size_t *nentries, i (*list)[idx].protocol = sockets[i].ki_protocol; (*list)[idx].address.family = sockets[i].ki_family; (*list)[idx].kvaddr = sockets[i].ki_sockaddr; + (*list)[idx].state = (sockets[i].ki_protocol == IPPROTO_TCP) ? (int32_t)sockets[i].ki_tstate : -1; if (sockets[i].ki_family == AF_INET) { diff --git a/src/platform/bsd/native/openbsd.c b/src/platform/bsd/native/openbsd.c index 4a32418..9c4a55c 100644 --- a/src/platform/bsd/native/openbsd.c +++ b/src/platform/bsd/native/openbsd.c @@ -101,6 +101,7 @@ int openbsd_fetch_sockets_by_pid(pid_t pid, struct socket_info_t **list, size_t (*list)[idx].port = ntohs(files[i].inp_lport); (*list)[idx].address.family = files[i].so_family; memcpy(&((*list)[idx].address.addr), files[i].inp_laddru, sizeof(files[i].inp_laddru)); + (*list)[idx].state = (files[i].so_protocol == IPPROTO_TCP) ? (int32_t)files[i].t_state : -1; ++idx; } diff --git a/src/platform/bsd/netbsd.rs b/src/platform/bsd/netbsd.rs index dd7e4bf..f61e5ad 100644 --- a/src/platform/bsd/netbsd.rs +++ b/src/platform/bsd/netbsd.rs @@ -27,6 +27,7 @@ pub(crate) fn get_all() -> crate::Result> { path, socket.address, socket.protocol, + socket.state, )); } } diff --git a/src/platform/bsd/openbsd.rs b/src/platform/bsd/openbsd.rs index 5b33446..dc2e0db 100644 --- a/src/platform/bsd/openbsd.rs +++ b/src/platform/bsd/openbsd.rs @@ -18,6 +18,7 @@ pub(crate) fn get_all() -> crate::Result> { String::new(), socket.address, socket.protocol, + socket.state, )); } } diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 5897582..567500e 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -24,6 +24,7 @@ pub(crate) fn get_all() -> crate::Result> { p.path(), proto_listener.local_addr(), proto_listener.protocol(), + proto_listener.state(), ); listeners.insert(listener); } diff --git a/src/platform/linux/proto_listener.rs b/src/platform/linux/proto_listener.rs index 97badfd..0e8a5de 100644 --- a/src/platform/linux/proto_listener.rs +++ b/src/platform/linux/proto_listener.rs @@ -1,4 +1,4 @@ -use crate::Protocol; +use crate::{Protocol, SocketState}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; @@ -9,6 +9,7 @@ pub(super) struct ProtoListener { local_addr: SocketAddr, inode: u64, protocol: Protocol, + state: SocketState, } impl ProtoListener { @@ -24,6 +25,10 @@ impl ProtoListener { self.protocol } + pub(super) fn state(&self) -> SocketState { + self.state + } + pub(super) fn get_all() -> crate::Result> { let mut table = Vec::new(); @@ -117,8 +122,8 @@ impl ProtoListener { let mut s = line.split_whitespace(); let local_addr_hex = s.nth(1).ok_or("Failed to get local address")?; - // consider all states - let _ = s.nth(1).ok_or("Failed to get state")?; + let state_hex = s.nth(1).ok_or("Failed to get state")?; + let state = SocketState::from_linux(u8::from_str_radix(state_hex, 16)?); let local_ip_port = local_addr_hex .split(':') @@ -138,6 +143,7 @@ impl ProtoListener { local_addr, inode, protocol, + state, }) } @@ -150,8 +156,8 @@ impl ProtoListener { let mut s = line.split_whitespace(); let local_addr_hex = s.nth(1).ok_or("Failed to get local address")?; - // consider all states - let _ = s.nth(1).ok_or("Failed to get state")?; + let state_hex = s.nth(1).ok_or("Failed to get state")?; + let state = SocketState::from_linux(u8::from_str_radix(state_hex, 16)?); let mut local_ip_port = local_addr_hex.split(':'); @@ -190,6 +196,7 @@ impl ProtoListener { local_addr, inode, protocol, + state, }) } } diff --git a/src/platform/macos/c_socket_fd_info.rs b/src/platform/macos/c_socket_fd_info.rs index bb7276f..2eb95ce 100644 --- a/src/platform/macos/c_socket_fd_info.rs +++ b/src/platform/macos/c_socket_fd_info.rs @@ -3,8 +3,8 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use byteorder::{ByteOrder, NetworkEndian}; -use crate::Protocol; use crate::platform::macos::proto_listener::ProtoListener; +use crate::{Protocol, SocketState}; use super::statics::{IPPROTO_TCP, IPPROTO_UDP}; @@ -20,10 +20,16 @@ impl CSocketFdInfo { let family = sock_info.soi_family; let transport_protocol = sock_info.soi_protocol; - let general_sock_info = unsafe { + let (general_sock_info, state) = unsafe { match transport_protocol { - IPPROTO_TCP => sock_info.soi_proto.pri_tcp.tcpsi_ini, - IPPROTO_UDP => sock_info.soi_proto.pri_in, + IPPROTO_TCP => { + let tcp_info = sock_info.soi_proto.pri_tcp; + ( + tcp_info.tcpsi_ini, + SocketState::from_bsd(tcp_info.tcpsi_state), + ) + } + IPPROTO_UDP => (sock_info.soi_proto.pri_in, SocketState::Unknown), _ => return Err("Unsupported protocol".into()), } }; @@ -36,6 +42,7 @@ impl CSocketFdInfo { local_address, NetworkEndian::read_u16(&lport_bytes), protocol, + state, ); Ok(socket_info) diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index 8c43143..b6625ac 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -41,6 +41,7 @@ pub(crate) fn get_all() -> crate::Result> { path, proto_listener.socket_addr(), proto_listener.protocol(), + proto_listener.state(), ); listeners.insert(listener); } diff --git a/src/platform/macos/proto_listener.rs b/src/platform/macos/proto_listener.rs index 48f89e3..a9de520 100644 --- a/src/platform/macos/proto_listener.rs +++ b/src/platform/macos/proto_listener.rs @@ -9,19 +9,21 @@ use crate::platform::macos::proc_pid::ProcPid; use crate::platform::macos::socket_fd::SocketFd; use crate::platform::macos::statics::PROC_PID_FD_SOCKET_INFO; -use crate::Protocol; +use crate::{Protocol, SocketState}; #[derive(Debug)] pub(super) struct ProtoListener { local_addr: SocketAddr, protocol: Protocol, + state: SocketState, } impl ProtoListener { - pub(super) fn new(addr: IpAddr, port: u16, protocol: Protocol) -> Self { + pub(super) fn new(addr: IpAddr, port: u16, protocol: Protocol, state: SocketState) -> Self { ProtoListener { local_addr: SocketAddr::new(addr, port), protocol, + state, } } @@ -33,6 +35,10 @@ impl ProtoListener { self.protocol } + pub(super) fn state(&self) -> SocketState { + self.state + } + pub(super) fn from_pid_fd(pid: ProcPid, fd: &SocketFd) -> crate::Result { let mut sinfo: MaybeUninit = MaybeUninit::uninit(); diff --git a/src/platform/windows/proto_listener.rs b/src/platform/windows/proto_listener.rs index 739609a..97f1c9e 100644 --- a/src/platform/windows/proto_listener.rs +++ b/src/platform/windows/proto_listener.rs @@ -1,5 +1,6 @@ use crate::Listener; use crate::Protocol; +use crate::SocketState; use crate::platform::windows::socket_table::SocketTable; use crate::platform::windows::tcp_table::TcpTable; use crate::platform::windows::tcp6_table::Tcp6Table; @@ -29,6 +30,7 @@ pub(super) struct ProtoListener { local_port: u16, pub(super) pid: u32, protocol: Protocol, + state: SocketState, } impl ProtoListener { @@ -74,12 +76,19 @@ impl ProtoListener { Err("No listener found on port".into()) } - pub(super) fn new(local_addr: IpAddr, local_port: u16, pid: u32, protocol: Protocol) -> Self { + pub(super) fn new( + local_addr: IpAddr, + local_port: u16, + pid: u32, + protocol: Protocol, + state: SocketState, + ) -> Self { Self { local_addr, local_port, pid, protocol, + state, } } } @@ -128,7 +137,14 @@ impl PidNamePathCache { .flatten() .map(|(pname, ppath)| { let socket = SocketAddr::new(proto_listener.local_addr, proto_listener.local_port); - Listener::new(pid, pname, ppath, socket, proto_listener.protocol) + Listener::new( + pid, + pname, + ppath, + socket, + proto_listener.protocol, + proto_listener.state, + ) }) } } diff --git a/src/platform/windows/socket_table.rs b/src/platform/windows/socket_table.rs index 5fd8a0f..775413d 100644 --- a/src/platform/windows/socket_table.rs +++ b/src/platform/windows/socket_table.rs @@ -3,6 +3,7 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use super::statics::UDP_TABLE_OWNER_PID; use crate::Protocol; +use crate::SocketState; use crate::platform::target_os::proto_listener::ProtoListener; use crate::platform::windows::statics::{ AF_INET, AF_INET6, ERROR_INSUFFICIENT_BUFFER, NO_ERROR, TCP_TABLE_OWNER_PID_ALL, @@ -56,6 +57,7 @@ impl SocketTable for TcpTable { port, row.owning_pid, Protocol::TCP, + SocketState::from_windows(row.state), )) } } @@ -93,6 +95,7 @@ impl SocketTable for Tcp6Table { port, row.owning_pid, Protocol::TCP, + SocketState::from_windows(row.state), )) } } @@ -130,6 +133,7 @@ impl SocketTable for UdpTable { port, row.owning_pid, Protocol::UDP, + SocketState::Unknown, )) } } @@ -167,6 +171,7 @@ impl SocketTable for Udp6Table { port, row.owning_pid, Protocol::UDP, + SocketState::Unknown, )) } } diff --git a/tests/integration.rs b/tests/integration.rs index ea73ed9..cacb046 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,5 +1,5 @@ use http_test_server::TestServer; -use listeners::{Listener, Process, Protocol, get_process_by_port}; +use listeners::{Listener, Process, Protocol, SocketState, get_process_by_port}; use rand::prelude::IteratorRandom; use serial_test::serial; use std::collections::HashSet; @@ -98,7 +98,8 @@ fn test_http_server() { path: http_server_path }, socket: SocketAddr::from_str(&format!("127.0.0.1:{http_server_port}")).unwrap(), - protocol: Protocol::TCP + protocol: Protocol::TCP, + state: SocketState::Listen, } ); } @@ -221,6 +222,123 @@ fn test_tcp6() { assert!(all_found); } +#[test] +#[serial] +fn test_tcp_listen_state() { + let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + let socket = TcpListener::bind(SocketAddr::new(ip, 0)).unwrap(); + let port = socket.local_addr().unwrap().port(); + + let all = listeners::get_all().unwrap(); + let listener = all + .iter() + .find(|l| l.socket.port() == port && l.protocol == Protocol::TCP) + .unwrap(); + assert_eq!(listener.state, SocketState::Listen); +} + +#[test] +#[serial] +fn test_tcp_established_state() { + let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + let server = TcpListener::bind(SocketAddr::new(ip, 0)).unwrap(); + let port = server.local_addr().unwrap().port(); + + let _client = std::net::TcpStream::connect(server.local_addr().unwrap()).unwrap(); + let (_accepted, _) = server.accept().unwrap(); + + let all = listeners::get_all().unwrap(); + let has_established = all + .iter() + .any(|l| l.socket.port() == port && l.state == SocketState::Established); + assert!( + has_established, + "Expected an established TCP connection on port {port}" + ); +} + +#[test] +#[serial] +fn test_tcp_listen_state_ipv6() { + let ip = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); + let socket = TcpListener::bind(SocketAddr::new(ip, 0)).unwrap(); + let port = socket.local_addr().unwrap().port(); + + let all = listeners::get_all().unwrap(); + let listener = all + .iter() + .find(|l| l.socket.port() == port && l.protocol == Protocol::TCP) + .unwrap(); + assert_eq!(listener.state, SocketState::Listen); +} + +#[test] +#[serial] +fn test_tcp_established_state_ipv6() { + let ip = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); + let server = TcpListener::bind(SocketAddr::new(ip, 0)).unwrap(); + let port = server.local_addr().unwrap().port(); + + let _client = std::net::TcpStream::connect(server.local_addr().unwrap()).unwrap(); + let (_accepted, _) = server.accept().unwrap(); + + let all = listeners::get_all().unwrap(); + let has_established = all + .iter() + .any(|l| l.socket.port() == port && l.state == SocketState::Established); + assert!( + has_established, + "Expected an established IPv6 TCP connection on port {port}" + ); +} + +#[test] +#[serial] +fn test_tcp_close_wait_state() { + use std::io::Read; + + let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + let server = TcpListener::bind(SocketAddr::new(ip, 0)).unwrap(); + let port = server.local_addr().unwrap().port(); + + let client = std::net::TcpStream::connect(server.local_addr().unwrap()).unwrap(); + let (mut accepted, _) = server.accept().unwrap(); + + // Drop client to send FIN; read until EOF to confirm the FIN has been received + // before sampling state, ensuring the kernel has moved accepted into CloseWait. + drop(client); + let mut buf = [0u8; 1]; + let _ = accepted.read(&mut buf); + + let all = listeners::get_all().unwrap(); + let has_close_wait = all + .iter() + .any(|l| l.socket.port() == port && l.state == SocketState::CloseWait); + assert!( + has_close_wait, + "Expected a CloseWait TCP connection on port {port}" + ); +} + +#[test] +#[serial] +fn test_udp_state_is_unknown() { + let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + let socket = UdpSocket::bind(SocketAddr::new(ip, 0)).unwrap(); + let port = socket.local_addr().unwrap().port(); + + let all = listeners::get_all().unwrap(); + let listener = all + .iter() + .find(|l| l.socket.port() == port && l.protocol == Protocol::UDP) + .unwrap(); + // Linux reports raw 0x07 for UDP, which maps to Closed; other platforms use Unknown + #[cfg(target_os = "linux")] + assert_eq!(listener.state, SocketState::Closed); + #[cfg(not(target_os = "linux"))] + assert_eq!(listener.state, SocketState::Unknown); +} + #[test] #[serial] fn test_udp6() { From 3ffc364a306286c0b304159e21fe50b3d968c55b Mon Sep 17 00:00:00 2001 From: Anton Liashkevich Date: Fri, 8 May 2026 23:27:08 -0400 Subject: [PATCH 2/8] Map UDP socket state to Unknown on Linux --- src/platform/linux/proto_listener.rs | 10 ++++++++-- tests/integration.rs | 4 ---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/platform/linux/proto_listener.rs b/src/platform/linux/proto_listener.rs index 0e8a5de..07422c2 100644 --- a/src/platform/linux/proto_listener.rs +++ b/src/platform/linux/proto_listener.rs @@ -123,7 +123,10 @@ impl ProtoListener { let local_addr_hex = s.nth(1).ok_or("Failed to get local address")?; let state_hex = s.nth(1).ok_or("Failed to get state")?; - let state = SocketState::from_linux(u8::from_str_radix(state_hex, 16)?); + let state = match protocol { + Protocol::TCP => SocketState::from_linux(u8::from_str_radix(state_hex, 16)?), + Protocol::UDP => SocketState::Unknown, + }; let local_ip_port = local_addr_hex .split(':') @@ -157,7 +160,10 @@ impl ProtoListener { let local_addr_hex = s.nth(1).ok_or("Failed to get local address")?; let state_hex = s.nth(1).ok_or("Failed to get state")?; - let state = SocketState::from_linux(u8::from_str_radix(state_hex, 16)?); + let state = match protocol { + Protocol::TCP => SocketState::from_linux(u8::from_str_radix(state_hex, 16)?), + Protocol::UDP => SocketState::Unknown, + }; let mut local_ip_port = local_addr_hex.split(':'); diff --git a/tests/integration.rs b/tests/integration.rs index cacb046..a48223a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -332,10 +332,6 @@ fn test_udp_state_is_unknown() { .iter() .find(|l| l.socket.port() == port && l.protocol == Protocol::UDP) .unwrap(); - // Linux reports raw 0x07 for UDP, which maps to Closed; other platforms use Unknown - #[cfg(target_os = "linux")] - assert_eq!(listener.state, SocketState::Closed); - #[cfg(not(target_os = "linux"))] assert_eq!(listener.state, SocketState::Unknown); } From 4049e2ca16561b61361bec9e7db9db9babd6f3a5 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Wed, 10 Jun 2026 13:30:00 +0200 Subject: [PATCH 3/8] parse TCP state hex inside SocketState::from_linux --- src/lib.rs | 26 +++++++++++++------------- src/platform/linux/proto_listener.rs | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0e07dfc..7e50c5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,19 +84,19 @@ pub enum SocketState { impl SocketState { #[cfg(target_os = "linux")] - pub(crate) fn from_linux(raw: u8) -> Self { - match raw { - 0x01 => Self::Established, - 0x02 => Self::SynSent, - 0x03 => Self::SynReceived, - 0x04 => Self::FinWait1, - 0x05 => Self::FinWait2, - 0x06 => Self::TimeWait, - 0x07 => Self::Closed, - 0x08 => Self::CloseWait, - 0x09 => Self::LastAck, - 0x0A => Self::Listen, - 0x0B => Self::Closing, + 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, } } diff --git a/src/platform/linux/proto_listener.rs b/src/platform/linux/proto_listener.rs index 07422c2..655282a 100644 --- a/src/platform/linux/proto_listener.rs +++ b/src/platform/linux/proto_listener.rs @@ -124,7 +124,7 @@ impl ProtoListener { let local_addr_hex = s.nth(1).ok_or("Failed to get local address")?; let state_hex = s.nth(1).ok_or("Failed to get state")?; let state = match protocol { - Protocol::TCP => SocketState::from_linux(u8::from_str_radix(state_hex, 16)?), + Protocol::TCP => SocketState::from_linux(state_hex), Protocol::UDP => SocketState::Unknown, }; @@ -161,7 +161,7 @@ impl ProtoListener { let local_addr_hex = s.nth(1).ok_or("Failed to get local address")?; let state_hex = s.nth(1).ok_or("Failed to get state")?; let state = match protocol { - Protocol::TCP => SocketState::from_linux(u8::from_str_radix(state_hex, 16)?), + Protocol::TCP => SocketState::from_linux(state_hex), Protocol::UDP => SocketState::Unknown, }; From 2ad5b42105abf7552e80a22de2103ad3e201dc59 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Wed, 10 Jun 2026 15:45:25 +0200 Subject: [PATCH 4/8] tidy listener display formatting and update docs --- CHANGELOG.md | 4 ++++ README.md | 26 +++++++++++++------------- examples/print_paths.rs | 5 +---- src/lib.rs | 39 ++++++++++++++++++++------------------- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d62286c..fd0463e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All releases with the relative changes are documented in this file. +## [Unreleased] +### Added +- `state` field on `Listener` exposing the socket's TCP connection state via the new `SocketState` enum (`UNKNOWN` for UDP) + ## [0.5.1] - 2026-03-02 ### Fixed - Include build.rs in the published crate diff --git a/README.md b/README.md index 9fb7bd3..55ec3ae 100644 --- a/README.md +++ b/README.md @@ -58,19 +58,19 @@ if let Ok(listeners) = listeners::get_all() { Output: ``` text -PID: 440 Process name: ControlCenter Socket: 0.0.0.0:0 Protocol: UDP -PID: 456 Process name: rapportd Socket: [::]:49158 Protocol: TCP -PID: 456 Process name: rapportd Socket: 0.0.0.0:49158 Protocol: TCP -PID: 456 Process name: rapportd Socket: 0.0.0.0:0 Protocol: UDP -PID: 485 Process name: sharingd Socket: 0.0.0.0:0 Protocol: UDP -PID: 516 Process name: WiFiAgent Socket: 0.0.0.0:0 Protocol: UDP -PID: 1480 Process name: rustrover Socket: [::7f00:1]:63342 Protocol: TCP -PID: 2123 Process name: Telegram Socket: 192.168.1.102:49659 Protocol: TCP -PID: 2123 Process name: Telegram Socket: 192.168.1.102:49656 Protocol: TCP -PID: 2156 Process name: Google Chrome Socket: 0.0.0.0:0 Protocol: UDP -PID: 2167 Process name: Google Chrome Helper Socket: 192.168.1.102:60834 Protocol: UDP -PID: 2167 Process name: Google Chrome Helper Socket: 192.168.1.102:53220 Protocol: UDP -PID: 2167 Process name: Google Chrome Helper Socket: 192.168.1.102:59216 Protocol: UDP +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 ``` For more examples of usage, including how to get listening processes in a more granular way, diff --git a/examples/print_paths.rs b/examples/print_paths.rs index 4771b31..f90ba92 100644 --- a/examples/print_paths.rs +++ b/examples/print_paths.rs @@ -2,10 +2,7 @@ fn main() { // Retrieve all listeners and print their process paths if let Ok(listeners) = listeners::get_all() { for l in listeners { - println!( - "PID: {:<10} Process path: {}", - l.process.pid, l.process.path - ); + println!("PID: {:<7} Process path: {}", l.process.pid, l.process.path); } } } diff --git a/src/lib.rs b/src/lib.rs index 7e50c5b..bb79bd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,19 +157,19 @@ impl SocketState { /// /// Output: /// ``` text -/// PID: 440 Process name: ControlCenter Socket: 0.0.0.0:0 Protocol: UDP -/// PID: 456 Process name: rapportd Socket: [::]:49158 Protocol: TCP -/// PID: 456 Process name: rapportd Socket: 0.0.0.0:49158 Protocol: TCP -/// PID: 456 Process name: rapportd Socket: 0.0.0.0:0 Protocol: UDP -/// PID: 485 Process name: sharingd Socket: 0.0.0.0:0 Protocol: UDP -/// PID: 516 Process name: WiFiAgent Socket: 0.0.0.0:0 Protocol: UDP -/// PID: 1480 Process name: rustrover Socket: [::7f00:1]:63342 Protocol: TCP -/// PID: 2123 Process name: Telegram Socket: 192.168.1.102:49659 Protocol: TCP -/// PID: 2123 Process name: Telegram Socket: 192.168.1.102:49656 Protocol: TCP -/// PID: 2156 Process name: Google Chrome Socket: 0.0.0.0:0 Protocol: UDP -/// PID: 2167 Process name: Google Chrome Helper Socket: 192.168.1.102:60834 Protocol: UDP -/// PID: 2167 Process name: Google Chrome Helper Socket: 192.168.1.102:53220 Protocol: UDP -/// PID: 2167 Process name: Google Chrome Helper Socket: 192.168.1.102:59216 Protocol: UDP +/// 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> { platform::get_all() @@ -194,7 +194,7 @@ pub fn get_all() -> Result> { /// /// Output: /// ``` text -/// PID: 2123 Process name: Telegram +/// PID: 2123 Process name: Telegram /// ``` pub fn get_process_by_port(port: u16, protocol: Protocol) -> Result { if port == 0 { @@ -238,9 +238,10 @@ impl Display for Listener { state, } = self; let process = process.to_string(); + let protocol = protocol.to_string(); write!( f, - "{process:<55} Socket: {socket:<30} Protocol: {protocol} State: {state}" + "{process:<52} Socket: {socket:<30} Protocol: {protocol:<7} State: {state}" ) } } @@ -267,7 +268,7 @@ impl Display for SocketState { impl Display for Process { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Process { pid, name, .. } = self; - write!(f, "PID: {pid:<10} Process name: {name}") + write!(f, "PID: {pid:<7} Process name: {name}") } } @@ -298,7 +299,7 @@ mod tests { ); assert_eq!( listener.to_string(), - "PID: 455 Process name: rapportd Socket: 0.0.0.0:51189 Protocol: TCP State: LISTEN" + "PID: 455 Process name: rapportd Socket: 0.0.0.0:51189 Protocol: TCP State: LISTEN" ); } @@ -314,7 +315,7 @@ mod tests { ); assert_eq!( listener.to_string(), - "PID: 160 Process name: mysqld Socket: [::]:3306 Protocol: UDP State: UNKNOWN" + "PID: 160 Process name: mysqld Socket: [::]:3306 Protocol: UDP State: UNKNOWN" ); } @@ -327,7 +328,7 @@ mod tests { ); assert_eq!( process.to_string(), - "PID: 611 Process name: Microsoft SharePoint" + "PID: 611 Process name: Microsoft SharePoint" ); } } From 00a5673524adea2a90ba9ae19832623628d44d7b Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Wed, 10 Jun 2026 15:54:10 +0200 Subject: [PATCH 5/8] update CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd0463e..aad2f18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ All releases with the relative changes are documented in this file. ## [Unreleased] ### Added -- `state` field on `Listener` exposing the socket's TCP connection state via the new `SocketState` enum (`UNKNOWN` for UDP) +- Added `state` field to `Listener` struct, exposing the socket's TCP connection state ([#49](https://github.com/GyulyVGC/listeners/pull/49)) +- New `SocketState` enum ([#49](https://github.com/GyulyVGC/listeners/pull/49)) ## [0.5.1] - 2026-03-02 ### Fixed From 8be81895d32e7c3b601a3b4d548eae1f73af8bdc Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Wed, 10 Jun 2026 16:04:04 +0200 Subject: [PATCH 6/8] add unit tests for SocketState::from_* methods --- src/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index bb79bd2..55dc93f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -331,4 +331,67 @@ mod tests { "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); + // 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); + } } From 44e53b9d1f1e74cf7f7fd2bec4dd1db350fd335f Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Wed, 10 Jun 2026 16:18:54 +0200 Subject: [PATCH 7/8] minor fix to FIN_WAIT states --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 55dc93f..6e59cb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,9 +62,9 @@ pub enum SocketState { SynSent, /// Received a connection request. SynReceived, - /// First step of the four-way closing handshake. + /// Sent a FIN, waiting for its ACK or the peer's FIN. FinWait1, - /// Second step of the four-way closing handshake. + /// FIN acknowledged, waiting for the peer's FIN. FinWait2, /// Waiting for remaining packets after close. TimeWait, @@ -252,8 +252,8 @@ impl Display for SocketState { SocketState::Established => write!(f, "ESTABLISHED"), SocketState::SynSent => write!(f, "SYN_SENT"), SocketState::SynReceived => write!(f, "SYN_RECEIVED"), - SocketState::FinWait1 => write!(f, "FIN_WAIT1"), - SocketState::FinWait2 => write!(f, "FIN_WAIT2"), + 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"), From d39fd1ff00911d1e0350384413b1a625fac4b744 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Wed, 10 Jun 2026 16:27:05 +0200 Subject: [PATCH 8/8] reorder integration tests --- tests/integration.rs | 70 ++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index a48223a..088407c 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -222,6 +222,41 @@ fn test_tcp6() { assert!(all_found); } +#[test] +#[serial] +fn test_udp6() { + let mut opened_ports: Vec = Vec::new(); + let mut sockets: Vec = Vec::new(); + + let ip_addr = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); + let mut current_port = 5600; + let num_sockets = 10; + + for _ in 0..num_sockets { + let socket = UdpSocket::bind(SocketAddr::new(ip_addr, current_port)).unwrap(); + current_port = socket.local_addr().unwrap().port(); + opened_ports.push(current_port); + sockets.push(socket); + current_port += 1; + } + + let all_listeners = listeners::get_all().unwrap(); + let all_found = opened_ports.iter().all(|p| { + let l = all_listeners + .iter() + .find(|l| l.socket.port() == *p && l.protocol == Protocol::UDP) + .unwrap(); + let process_by_port = get_process_by_port(l.socket.port(), Protocol::UDP).unwrap(); + // assert that the process name and path are not empty + assert!(!l.process.name.is_empty()); + #[cfg(not(target_os = "openbsd"))] + assert!(!l.process.path.is_empty()); + l.process == process_by_port + }); + + assert!(all_found); +} + #[test] #[serial] fn test_tcp_listen_state() { @@ -334,38 +369,3 @@ fn test_udp_state_is_unknown() { .unwrap(); assert_eq!(listener.state, SocketState::Unknown); } - -#[test] -#[serial] -fn test_udp6() { - let mut opened_ports: Vec = Vec::new(); - let mut sockets: Vec = Vec::new(); - - let ip_addr = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); - let mut current_port = 5600; - let num_sockets = 10; - - for _ in 0..num_sockets { - let socket = UdpSocket::bind(SocketAddr::new(ip_addr, current_port)).unwrap(); - current_port = socket.local_addr().unwrap().port(); - opened_ports.push(current_port); - sockets.push(socket); - current_port += 1; - } - - let all_listeners = listeners::get_all().unwrap(); - let all_found = opened_ports.iter().all(|p| { - let l = all_listeners - .iter() - .find(|l| l.socket.port() == *p && l.protocol == Protocol::UDP) - .unwrap(); - let process_by_port = get_process_by_port(l.socket.port(), Protocol::UDP).unwrap(); - // assert that the process name and path are not empty - assert!(!l.process.name.is_empty()); - #[cfg(not(target_os = "openbsd"))] - assert!(!l.process.path.is_empty()); - l.process == process_by_port - }); - - assert!(all_found); -}