diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a2114d..6892d9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ on: pull_request: branches: - '*' + workflow_dispatch: env: CARGO_TERM_COLOR: always diff --git a/src/lib.rs b/src/lib.rs index 3245d9f..5536811 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ pub struct Listener { pub process: Process, /// The TCP socket this listener is listening on. pub socket: SocketAddr, + pub protocol: Protocol, } /// A process, characterized by its PID and name. @@ -26,6 +27,11 @@ pub struct Process { pub name: String, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Protocol { + TCP, + UDP, +} /// Returns all the [Listener]s. /// /// # Errors @@ -144,9 +150,13 @@ pub fn get_ports_by_process_name(name: &str) -> Result> { } impl Listener { - fn new(pid: u32, name: String, socket: SocketAddr) -> Self { + fn new(pid: u32, name: String, socket: SocketAddr, protocol: Protocol) -> Self { let process = Process::new(pid, name); - Self { process, socket } + Self { + process, + socket, + protocol, + } } } @@ -158,9 +168,13 @@ impl Process { impl Display for Listener { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Listener { process, socket } = self; + let Listener { + process, + socket, + protocol, + } = self; let process = process.to_string(); - write!(f, "{process:<55} Socket: {socket}",) + write!(f, "{process:<55} Socket: {socket:<30} Protocol: {protocol}",) } } @@ -171,11 +185,20 @@ impl Display for Process { } } +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}; + use crate::{Listener, Process, Protocol}; #[test] fn test_v4_listener_to_string() { @@ -183,10 +206,11 @@ mod tests { 455, "rapportd".to_string(), SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 51189), + Protocol::TCP, ); assert_eq!( listener.to_string(), - "PID: 455 Process name: rapportd Socket: 0.0.0.0:51189" + "PID: 455 Process name: rapportd Socket: 0.0.0.0:51189 Protocol: TCP" ); } @@ -196,10 +220,11 @@ mod tests { 160, "mysqld".to_string(), SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 3306), + Protocol::TCP, ); assert_eq!( listener.to_string(), - "PID: 160 Process name: mysqld Socket: [::]:3306" + "PID: 160 Process name: mysqld Socket: [::]:3306 Protocol: TCP" ); } diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 7dbfab4..11a8c76 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -19,7 +19,12 @@ pub(crate) fn get_all() -> crate::Result> { for tcp_listener in TcpListener::get_all()? { if let Some(p) = inode_proc_map.get(&tcp_listener.inode()) { - let listener = Listener::new(p.pid(), p.name(), tcp_listener.local_addr()); + let listener = Listener::new( + p.pid(), + p.name(), + tcp_listener.local_addr(), + tcp_listener.protocol(), + ); listeners.insert(listener); } } diff --git a/src/platform/linux/tcp_listener.rs b/src/platform/linux/tcp_listener.rs index 6a2fa8d..2d6e484 100644 --- a/src/platform/linux/tcp_listener.rs +++ b/src/platform/linux/tcp_listener.rs @@ -1,3 +1,4 @@ +use crate::Protocol; use std::fs::File; use std::io::{BufRead, BufReader}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; @@ -7,10 +8,11 @@ use std::str::FromStr; pub(super) struct TcpListener { local_addr: SocketAddr, inode: u64, + protocol: Protocol, } impl TcpListener { - const LISTEN_STATE: &'static str = "0A"; + // const LISTEN_STATE: &'static str = "0A"; pub(super) fn local_addr(&self) -> SocketAddr { self.local_addr @@ -20,30 +22,50 @@ impl TcpListener { self.inode } + pub(super) fn protocol(&self) -> Protocol { + self.protocol + } + pub(super) fn get_all() -> crate::Result> { let mut table = Vec::new(); let tcp_table = File::open("/proc/net/tcp")?; for line in BufReader::new(tcp_table).lines().map_while(Result::ok) { - if let Ok(l) = TcpListener::from_tcp_table_entry(&line) { + if let Ok(l) = TcpListener::from_protocol_table_entry(&line, Protocol::TCP) { table.push(l); } } + let tcp6_table = File::open("/proc/net/tcp6")?; for line in BufReader::new(tcp6_table).lines().map_while(Result::ok) { - if let Ok(l) = TcpListener::from_tcp6_table_entry(&line) { + if let Ok(l) = TcpListener::from_protocolv6_table_entry(&line, Protocol::TCP) { table.push(l); } } + + let udp_table = File::open("/proc/net/udp")?; + for line in BufReader::new(udp_table).lines().map_while(Result::ok) { + // the lines/fields for tcp and udp are identical as far as Listeners is concerend + if let Ok(l) = TcpListener::from_protocol_table_entry(&line, Protocol::UDP) { + table.push(l) + } + } + + let udp_table = File::open("/proc/net/udp6")?; + for line in BufReader::new(udp_table).lines().map_while(Result::ok) { + // the lines/fields for tcp and udp are identical as far as Listeners is concerend + if let Ok(l) = TcpListener::from_protocolv6_table_entry(&line, Protocol::UDP) { + table.push(l) + } + } Ok(table) } - fn from_tcp_table_entry(line: &str) -> crate::Result { + fn from_protocol_table_entry(line: &str, protocol: Protocol) -> crate::Result { let mut s = line.split_whitespace(); let local_addr_hex = s.nth(1).ok_or("Failed to get local address")?; - let Some(Self::LISTEN_STATE) = s.nth(1) else { - return Err("Not a listening socket".into()); - }; + // consider all states + let _ = s.nth(1).ok_or("Failed to get state")?; let local_ip_port = local_addr_hex .split(':') @@ -59,10 +81,14 @@ impl TcpListener { let inode_n = s.nth(5).ok_or("Failed to get inode")?; let inode = u64::from_str(inode_n)?; - Ok(Self { local_addr, inode }) + Ok(Self { + local_addr, + inode, + protocol, + }) } - fn from_tcp6_table_entry(line: &str) -> crate::Result { + fn from_protocolv6_table_entry(line: &str, protocol: Protocol) -> crate::Result { #[cfg(target_endian = "little")] let read_endian = u32::from_le_bytes; #[cfg(target_endian = "big")] @@ -71,9 +97,8 @@ impl TcpListener { let mut s = line.split_whitespace(); let local_addr_hex = s.nth(1).ok_or("Failed to get local address")?; - let Some(Self::LISTEN_STATE) = s.nth(1) else { - return Err("Not a listening socket".into()); - }; + // consider all states + let _ = s.nth(1).ok_or("Failed to get state")?; let mut local_ip_port = local_addr_hex.split(':'); @@ -108,6 +133,10 @@ impl TcpListener { let inode_n = s.nth(5).ok_or("Failed to get inode")?; let inode = u64::from_str(inode_n)?; - Ok(Self { local_addr, inode }) + Ok(Self { + local_addr, + inode, + protocol, + }) } } diff --git a/src/platform/macos/c_socket_fd_info.rs b/src/platform/macos/c_socket_fd_info.rs index 6917c57..740ee62 100644 --- a/src/platform/macos/c_socket_fd_info.rs +++ b/src/platform/macos/c_socket_fd_info.rs @@ -3,8 +3,10 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use byteorder::{ByteOrder, NetworkEndian}; -use crate::platform::macos::statics::SOCKET_STATE_LISTEN; use crate::platform::macos::tcp_listener::TcpListener; +use crate::Protocol; + +use super::statics::{IPPROTO_TCP, IPPROTO_UDP}; #[repr(C)] pub(super) struct CSocketFdInfo { @@ -16,18 +18,31 @@ impl CSocketFdInfo { pub(super) fn to_tcp_listener(&self) -> crate::Result { let sock_info = self.psi; let family = sock_info.soi_family; + let transport_protocol = sock_info.soi_protocol; - let tcp_in = unsafe { sock_info.soi_proto.pri_tcp }; + let general_sock_info = unsafe { + match transport_protocol { + IPPROTO_TCP => sock_info.soi_proto.pri_tcp.tcpsi_ini, + IPPROTO_UDP => sock_info.soi_proto.pri_in, + _ => return Err("Unsupported protocol".into()), + } + }; - if tcp_in.tcpsi_state != SOCKET_STATE_LISTEN { - return Err("Socket is not in listen state".into()); - } + // if tcp, do not filter on state (get em all) + // if tcp_in.tcpsi_state != SOCKET_STATE_LISTEN && ip_protocol == IPPROT_TCP { + // return Err("Socket is not in listening state".into()); + // } - let tcp_sockaddr_in = tcp_in.tcpsi_ini; - let lport_bytes: [u8; 4] = i32::to_le_bytes(tcp_sockaddr_in.insi_lport); - let local_address = Self::get_local_addr(family, tcp_sockaddr_in)?; + // let tcp_sockaddr_in = tcp_in.tcpsi_ini; + let lport_bytes: [u8; 4] = i32::to_le_bytes(general_sock_info.insi_lport); + let local_address = Self::get_local_addr(family, general_sock_info)?; + let protocol = Self::get_protocol(family, transport_protocol)?; - let socket_info = TcpListener::new(local_address, NetworkEndian::read_u16(&lport_bytes)); + let socket_info = TcpListener::new( + local_address, + NetworkEndian::read_u16(&lport_bytes), + protocol, + ); Ok(socket_info) } @@ -49,6 +64,14 @@ impl CSocketFdInfo { _ => Err("Unsupported socket family".into()), } } + + fn get_protocol(family: c_int, ip_protocol: c_int) -> crate::Result { + match (family, ip_protocol) { + (2 | 30, IPPROTO_TCP) => Ok(Protocol::TCP), + (2 | 30, IPPROTO_UDP) => Ok(Protocol::UDP), + (_, _) => Err("unsupported protocol".into()), + } + } } #[repr(C)] diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index 762b6d8..8b3e7d1 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -23,7 +23,12 @@ pub(crate) fn get_all() -> crate::Result> { for fd in SocketFd::get_all_of_pid(pid).iter().flatten() { if let Ok(tcp_listener) = TcpListener::from_pid_fd(pid, fd) { if let Ok(ProcName(name)) = ProcName::from_pid(pid) { - let listener = Listener::new(pid.as_u_32()?, name, tcp_listener.socket_addr()); + let listener = Listener::new( + pid.as_u_32()?, + name, + tcp_listener.socket_addr(), + tcp_listener.protocol(), + ); listeners.insert(listener); } } diff --git a/src/platform/macos/statics.rs b/src/platform/macos/statics.rs index d754e40..7608fbb 100644 --- a/src/platform/macos/statics.rs +++ b/src/platform/macos/statics.rs @@ -4,5 +4,7 @@ pub(super) const PROC_ALL_PIDS: u32 = 1; pub(super) const PROC_PID_LIST_FDS: c_int = 1; pub(super) const PROC_PID_FD_SOCKET_INFO: c_int = 3; pub(super) const FD_TYPE_SOCKET: u32 = 2; -pub(super) const SOCKET_STATE_LISTEN: c_int = 1; +// pub(super) const SOCKET_STATE_LISTEN: c_int = 1; pub(super) const PROC_PID_PATH_INFO_MAXSIZE: usize = 4096; +pub(super) const IPPROTO_TCP: c_int = 6; +pub(super) const IPPROTO_UDP: c_int = 17; diff --git a/src/platform/macos/tcp_listener.rs b/src/platform/macos/tcp_listener.rs index 901feb7..4f31337 100644 --- a/src/platform/macos/tcp_listener.rs +++ b/src/platform/macos/tcp_listener.rs @@ -9,16 +9,28 @@ 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; + #[derive(Debug)] -pub(super) struct TcpListener(SocketAddr); +pub(super) struct TcpListener { + local_addr: SocketAddr, + protocol: Protocol, +} impl TcpListener { - pub(super) fn new(addr: IpAddr, port: u16) -> Self { - TcpListener(SocketAddr::new(addr, port)) + pub(super) fn new(addr: IpAddr, port: u16, protocol: Protocol) -> Self { + TcpListener { + local_addr: SocketAddr::new(addr, port), + protocol, + } } pub(super) fn socket_addr(&self) -> SocketAddr { - self.0 + self.local_addr + } + + pub(super) fn protocol(&self) -> Protocol { + self.protocol } pub(super) fn from_pid_fd(pid: ProcPid, fd: &SocketFd) -> crate::Result { diff --git a/src/platform/windows/c_iphlpapi.rs b/src/platform/windows/c_iphlpapi.rs index d7ff4ae..89570c9 100644 --- a/src/platform/windows/c_iphlpapi.rs +++ b/src/platform/windows/c_iphlpapi.rs @@ -12,3 +12,16 @@ extern "system" { Reserved: c_ulong, ) -> c_ulong; } + +#[allow(non_snake_case)] +#[link(name = "iphlpapi")] +extern "system" { + pub(super) fn GetExtendedUdpTable( + pUdpTable: *mut c_void, + pdwSize: *mut c_ulong, + bOrder: c_int, + ulAf: c_ulong, + TableClass: c_ulong, + Reserved: c_ulong, + ) -> c_ulong; +} diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 7f840e2..7d740a4 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -10,6 +10,8 @@ mod statics; mod tcp6_table; mod tcp_listener; mod tcp_table; +mod udp6_table; +mod udp_table; pub(crate) fn get_all() -> crate::Result> { let mut listeners = HashSet::new(); diff --git a/src/platform/windows/socket_table.rs b/src/platform/windows/socket_table.rs index 753db4e..f797d2f 100644 --- a/src/platform/windows/socket_table.rs +++ b/src/platform/windows/socket_table.rs @@ -5,10 +5,16 @@ use crate::platform::target_os::c_iphlpapi::GetExtendedTcpTable; use crate::platform::target_os::statics::FALSE; use crate::platform::target_os::tcp_listener::TcpListener; use crate::platform::windows::statics::{ - AF_INET, AF_INET6, ERROR_INSUFFICIENT_BUFFER, LISTEN, NO_ERROR, TCP_TABLE_OWNER_PID_ALL, + AF_INET, AF_INET6, ERROR_INSUFFICIENT_BUFFER, NO_ERROR, TCP_TABLE_OWNER_PID_ALL, }; use crate::platform::windows::tcp6_table::Tcp6Table; use crate::platform::windows::tcp_table::TcpTable; +use crate::platform::windows::udp6_table::Udp6Table; +use crate::platform::windows::udp_table::UdpTable; +use crate::Protocol; + +use super::c_iphlpapi::GetExtendedUdpTable; +use super::statics::UDP_TABLE_OWNER_PID; pub(super) trait SocketTable { fn get_table() -> crate::Result>; @@ -32,15 +38,13 @@ impl SocketTable for TcpTable { let table = unsafe { &*(table.as_ptr().cast::()) }; let rows_ptr = std::ptr::addr_of!(table.rows[0]); let row = unsafe { &*rows_ptr.add(index) }; - if row.state == LISTEN { - Some(TcpListener::new( - IpAddr::V4(Ipv4Addr::from(u32::from_be(row.local_addr))), - u16::from_be(u16::try_from(row.local_port).ok()?), - row.owning_pid, - )) - } else { - None - } + // if row.state == LISTEN { // get all states + Some(TcpListener::new( + IpAddr::V4(Ipv4Addr::from(u32::from_be(row.local_addr))), + u16::from_be(u16::try_from(row.local_port).ok()?), + row.owning_pid, + Protocol::TCP, + )) } } @@ -60,16 +64,109 @@ impl SocketTable for Tcp6Table { let table = unsafe { &*(table.as_ptr().cast::()) }; let rows_ptr = std::ptr::addr_of!(table.rows[0]); let row = unsafe { &*rows_ptr.add(index) }; - if row.state == LISTEN { - Some(TcpListener::new( - IpAddr::V6(Ipv6Addr::from(row.local_addr)), - u16::from_be(u16::try_from(row.local_port).ok()?), - row.owning_pid, - )) - } else { - None + // if row.state == LISTEN { + Some(TcpListener::new( + IpAddr::V6(Ipv6Addr::from(row.local_addr)), + u16::from_be(u16::try_from(row.local_port).ok()?), + row.owning_pid, + Protocol::TCP, + )) + } +} + +impl SocketTable for UdpTable { + fn get_table() -> crate::Result> { + get_udp_table(AF_INET) + } + + fn get_rows_count(table: &[u8]) -> usize { + #[allow(clippy::cast_ptr_alignment)] + let table = unsafe { &*(table.as_ptr().cast::()) }; + table.rows_count as usize + } + + fn get_tcp_listener(table: &[u8], index: usize) -> Option { + #[allow(clippy::cast_ptr_alignment)] + let table = unsafe { &*(table.as_ptr().cast::()) }; + let rows_ptr = std::ptr::addr_of!(table.rows[0]); + let row = unsafe { &*rows_ptr.add(index) }; + Some(TcpListener::new( + IpAddr::V4(Ipv4Addr::from(u32::from_be(row.local_addr))), + u16::from_be(u16::try_from(row.local_port).ok()?), + row.owning_pid, + Protocol::UDP, + )) + } +} + +impl SocketTable for Udp6Table { + fn get_table() -> crate::Result> { + get_udp_table(AF_INET6) + } + + fn get_rows_count(table: &[u8]) -> usize { + #[allow(clippy::cast_ptr_alignment)] + let table = unsafe { &*(table.as_ptr().cast::()) }; + table.rows_count as usize + } + + fn get_tcp_listener(table: &[u8], index: usize) -> Option { + #[allow(clippy::cast_ptr_alignment)] + let table = unsafe { &*(table.as_ptr().cast::()) }; + let rows_ptr = std::ptr::addr_of!(table.rows[0]); + let row = unsafe { &*rows_ptr.add(index) }; + Some(TcpListener::new( + IpAddr::V6(Ipv6Addr::from(row.local_addr)), + u16::from_be(u16::try_from(row.local_port).ok()?), + row.owning_pid, + Protocol::UDP, + )) + } +} + +// fn get_protocol_table(address_family: c_ulong, protocol: Protocol) -> crate::Result> { +// match protocol { +// Protocol::TCP | Protocol::TCP6 => get_tcp_table(address_family), +// Protocol::UDP | Protocol::UDP6 => get_udp_table(address_family), +// } +// } + +fn get_udp_table(address_family: c_ulong) -> crate::Result> { + let mut table_size: c_ulong = 0; + let mut err_code = unsafe { + GetExtendedUdpTable( + std::ptr::null_mut(), + &mut table_size, + FALSE, + address_family, + UDP_TABLE_OWNER_PID, + 0, + ) + }; + let mut table = Vec::::new(); + let mut iterations = 0; + while err_code == ERROR_INSUFFICIENT_BUFFER { + table = Vec::::with_capacity(table_size as usize); + err_code = unsafe { + GetExtendedUdpTable( + table.as_mut_ptr().cast::(), + &mut table_size, + FALSE, + address_family, + UDP_TABLE_OWNER_PID, + 0, + ) + }; + iterations += 1; + if iterations > 100 { + return Err("Failed to allocate buffer".into()); } } + if err_code == NO_ERROR { + Ok(table) + } else { + Err("Failed to get UDP table".into()) + } } fn get_tcp_table(address_family: c_ulong) -> crate::Result> { diff --git a/src/platform/windows/statics.rs b/src/platform/windows/statics.rs index 33aa750..f8183db 100644 --- a/src/platform/windows/statics.rs +++ b/src/platform/windows/statics.rs @@ -1,9 +1,10 @@ use std::ffi::{c_int, c_ulong}; pub(super) const TCP_TABLE_OWNER_PID_ALL: c_ulong = 5; +pub(super) const UDP_TABLE_OWNER_PID: c_ulong = 1; pub(super) const FALSE: c_int = 0; pub(super) const ERROR_INSUFFICIENT_BUFFER: c_ulong = 0x7A; pub(super) const NO_ERROR: c_ulong = 0; pub(super) const AF_INET: c_ulong = 2; pub(super) const AF_INET6: c_ulong = 23; -pub(super) const LISTEN: c_ulong = 2; +// pub(super) const LISTEN: c_ulong = 2; diff --git a/src/platform/windows/tcp_listener.rs b/src/platform/windows/tcp_listener.rs index 3c9829a..5cf04a4 100644 --- a/src/platform/windows/tcp_listener.rs +++ b/src/platform/windows/tcp_listener.rs @@ -11,12 +11,17 @@ use crate::platform::windows::socket_table::SocketTable; use crate::platform::windows::tcp6_table::Tcp6Table; use crate::platform::windows::tcp_table::TcpTable; use crate::Listener; +use crate::Protocol; + +use super::udp6_table::Udp6Table; +use super::udp_table::UdpTable; #[derive(Debug)] pub(super) struct TcpListener { local_addr: IpAddr, local_port: u16, pid: u32, + protocol: Protocol, } impl TcpListener { @@ -25,6 +30,8 @@ impl TcpListener { .into_iter() .flatten() .chain(Self::table_entries::().into_iter().flatten()) + .chain(Self::table_entries::().into_iter().flatten()) + .chain(Self::table_entries::().into_iter().flatten()) .collect() } @@ -39,11 +46,12 @@ impl TcpListener { Ok(tcp_listeners) } - pub(super) fn new(local_addr: IpAddr, local_port: u16, pid: u32) -> Self { + pub(super) fn new(local_addr: IpAddr, local_port: u16, pid: u32, protocol: Protocol) -> Self { Self { local_addr, local_port, pid, + protocol, } } @@ -79,6 +87,6 @@ impl TcpListener { pub(super) fn to_listener(&self) -> Option { let socket = SocketAddr::new(self.local_addr, self.local_port); let pname = self.pname()?; - Some(Listener::new(self.pid, pname, socket)) + Some(Listener::new(self.pid, pname, socket, self.protocol)) } } diff --git a/src/platform/windows/udp6_table.rs b/src/platform/windows/udp6_table.rs new file mode 100644 index 0000000..650a19e --- /dev/null +++ b/src/platform/windows/udp6_table.rs @@ -0,0 +1,18 @@ +use std::ffi::c_uchar; +use std::os::raw::c_ulong; + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub(super) struct Udp6Table { + pub(super) rows_count: c_ulong, + pub(super) rows: [Udp6Row; 1], +} + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub(super) struct Udp6Row { + pub(super) local_addr: [c_uchar; 16], + local_scope_id: c_ulong, + pub(super) local_port: c_ulong, + pub(super) owning_pid: c_ulong, +} diff --git a/src/platform/windows/udp_table.rs b/src/platform/windows/udp_table.rs new file mode 100644 index 0000000..f2c43b3 --- /dev/null +++ b/src/platform/windows/udp_table.rs @@ -0,0 +1,16 @@ +use std::ffi::c_ulong; + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub(super) struct UdpTable { + pub(super) rows_count: c_ulong, + pub(super) rows: [UdpRow; 1], +} + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub(super) struct UdpRow { + pub(super) local_addr: c_ulong, + pub(super) local_port: c_ulong, + pub(super) owning_pid: c_ulong, +} diff --git a/tests/integration.rs b/tests/integration.rs index cff2103..ce964b7 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,10 +1,8 @@ -use std::net::SocketAddr; -use std::str::FromStr; - use http_test_server::TestServer; +use listeners::{Listener, Process, Protocol}; use serial_test::serial; - -use listeners::{Listener, Process}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, TcpListener, UdpSocket}; +use std::str::FromStr; #[test] #[serial] @@ -16,19 +14,32 @@ fn test_consistency() { let listeners = listeners::get_all().unwrap(); assert!(!listeners.is_empty()); - // check that the listeners retrieved by the different APIs are consistent - for l in listeners { - println!("{l}"); + // maybe there is no udp connection + if let Some(l_udp) = listeners.iter().find(|l| l.protocol == Protocol::UDP) { + println!("UDP: {l_udp}"); - let ports_by_pid = listeners::get_ports_by_pid(l.process.pid).unwrap(); - assert!(ports_by_pid.contains(&l.socket.port())); + let ports_by_name = listeners::get_ports_by_process_name(&l_udp.process.name).unwrap(); + assert!(ports_by_name.contains(&l_udp.socket.port())); - let ports_by_name = listeners::get_ports_by_process_name(&l.process.name).unwrap(); - assert!(ports_by_name.contains(&l.socket.port())); + let processes_by_port = listeners::get_processes_by_port(l_udp.socket.port()).unwrap(); + assert!(processes_by_port.contains(&l_udp.process)); - let processes_by_port = listeners::get_processes_by_port(l.socket.port()).unwrap(); - assert!(processes_by_port.contains(&l.process)); - } + let ports_by_pid = listeners::get_ports_by_pid(l_udp.process.pid).unwrap(); + assert!(ports_by_pid.contains(&l_udp.socket.port())); + }; + + if let Some(l_tcp) = listeners.iter().find(|l| l.protocol == Protocol::TCP) { + println!("TCP: {l_tcp}"); + + let ports_by_name = listeners::get_ports_by_process_name(&l_tcp.process.name).unwrap(); + assert!(ports_by_name.contains(&l_tcp.socket.port())); + + let processes_by_port = listeners::get_processes_by_port(l_tcp.socket.port()).unwrap(); + assert!(processes_by_port.contains(&l_tcp.process)); + + let ports_by_pid = listeners::get_ports_by_pid(l_tcp.process.pid).unwrap(); + assert!(ports_by_pid.contains(&l_tcp.socket.port())); + }; } #[test] @@ -73,7 +84,131 @@ fn test_http_server() { pid: http_server_pid, name: http_server_name }, - socket: SocketAddr::from_str(&format!("127.0.0.1:{http_server_port}")).unwrap() + socket: SocketAddr::from_str(&format!("127.0.0.1:{http_server_port}")).unwrap(), + protocol: Protocol::TCP } ); } + +#[test] +#[serial] +fn test_dns() { + let dns_port = 53; + let all = listeners::get_all().unwrap(); + let found = all.iter().any(|l| { + l.socket.port() == dns_port && l.protocol == Protocol::UDP || l.protocol == Protocol::TCP + }); + assert!(found); +} + +#[test] +#[serial] +fn test_udp() { + let mut opened_ports: Vec = Vec::new(); + let mut sockets: Vec = Vec::new(); + + let ip_addr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + let mut current_port = 1500; + 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| { + all_listeners + .iter() + .any(|l| l.socket.port() == *p && l.protocol == Protocol::UDP) + }); + + assert!(all_found); +} + +#[test] +#[serial] +fn test_tcp() { + let mut opened_ports: Vec = Vec::new(); + let mut sockets: Vec = Vec::new(); + + let ip_addr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + let mut current_port = 4500; + let num_sockets = 10; + + for _ in 0..num_sockets { + let socket = TcpListener::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| { + all_listeners + .iter() + .any(|l| l.socket.port() == *p && l.protocol == Protocol::TCP) + }); + + assert!(all_found); +} + +#[test] +#[serial] +fn test_tcp6() { + 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 = TcpListener::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| { + all_listeners + .iter() + .any(|l| l.socket.port() == *p && l.protocol == Protocol::TCP) + }); + + 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| { + all_listeners + .iter() + .any(|l| l.socket.port() == *p && l.protocol == Protocol::UDP) + }); + + assert!(all_found); +}