use crate::unix::UnixProcess;
use libproc::libproc::file_info::pidfdinfo;
use libproc::libproc::file_info::{ListFDs, ProcFDType};
use libproc::libproc::net_info::{SocketFDInfo, SocketInfoKind};
use libproc::libproc::proc_pid::{listpidinfo, name};
use libproc::processes::{pids_by_type, ProcFilter};
use log::debug;
use nix::unistd::Pid;
use std::collections::HashSet;
use std::io;
pub fn find_target_processes(port: u16) -> Result<Vec<UnixProcess>, io::Error> {
let mut target_pids: Vec<UnixProcess> = vec![];
let mut seen_pids: HashSet<i32> = HashSet::new();
if let Ok(procs) = pids_by_type(ProcFilter::All) {
'next_process: for p in procs {
let pid = p as i32;
if seen_pids.contains(&pid) {
continue;
}
let fds = listpidinfo::<ListFDs>(pid, 1024);
if let Ok(fds) = fds {
for fd in fds {
if let ProcFDType::Socket = fd.proc_fdtype.into() {
if let Ok(socket) = pidfdinfo::<SocketFDInfo>(pid, fd.proc_fd) {
let socket_kind = SocketInfoKind::from(socket.psi.soi_kind);
match socket_kind {
SocketInfoKind::In | SocketInfoKind::Tcp => {
let local_port = unsafe {
match socket_kind {
SocketInfoKind::In => {
socket.psi.soi_proto.pri_in.insi_lport as u16
}
SocketInfoKind::Tcp => {
socket.psi.soi_proto.pri_tcp.tcpsi_ini.insi_lport
as u16
}
_ => continue,
}
};
if u16::from_be(local_port) == port {
let process_name = name(pid).map_err(io::Error::other)?;
debug!(
"Found process '{}' with PID {} listening on port {}",
process_name, pid, port
);
seen_pids.insert(pid);
target_pids.push(UnixProcess::new(
Pid::from_raw(pid),
process_name,
));
continue 'next_process;
}
}
_ => (),
}
}
}
}
}
}
}
Ok(target_pids)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::killable::Killable;
use std::net::{TcpListener, UdpSocket};
#[test]
fn test_find_target_processes_no_listeners() {
let result = find_target_processes(19876);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_find_target_processes_tcp_listener() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
let result = find_target_processes(port);
assert!(result.is_ok());
let processes = result.unwrap();
assert!(
!processes.is_empty(),
"Expected to find at least one process on port {}",
port
);
for process in &processes {
assert!(!process.get_name().is_empty());
}
drop(listener);
}
#[test]
fn test_find_target_processes_correct_type() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
let processes = find_target_processes(port).unwrap();
for process in &processes {
assert_eq!(process.get_type(), crate::killable::KillableType::Process);
}
drop(listener);
}
#[test]
fn test_find_target_processes_ipv4() {
let listener = TcpListener::bind("0.0.0.0:0").unwrap();
let port = listener.local_addr().unwrap().port();
let processes = find_target_processes(port).unwrap();
assert!(
!processes.is_empty(),
"Expected to find process on 0.0.0.0:{}",
port
);
drop(listener);
}
#[test]
fn test_find_target_processes_after_close() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
drop(listener);
let processes = find_target_processes(port).unwrap();
let _ = processes;
}
#[test]
fn test_find_target_processes_multiple_on_different_ports() {
let listener1 = TcpListener::bind("127.0.0.1:0").unwrap();
let port1 = listener1.local_addr().unwrap().port();
let listener2 = TcpListener::bind("127.0.0.1:0").unwrap();
let port2 = listener2.local_addr().unwrap().port();
let processes1 = find_target_processes(port1).unwrap();
let processes2 = find_target_processes(port2).unwrap();
assert!(!processes1.is_empty());
assert!(!processes2.is_empty());
drop(listener1);
drop(listener2);
}
#[test]
fn test_find_target_processes_udp_listener() {
let socket = UdpSocket::bind("127.0.0.1:0").unwrap();
let port = socket.local_addr().unwrap().port();
let processes = find_target_processes(port).unwrap();
assert!(
!processes.is_empty(),
"Expected to find process with UDP socket on port {}",
port
);
drop(socket);
}
#[test]
fn test_find_target_processes_ipv6_tcp_listener() {
let listener = TcpListener::bind("[::1]:0").unwrap();
let port = listener.local_addr().unwrap().port();
let processes = find_target_processes(port).unwrap();
assert!(
!processes.is_empty(),
"Expected to find process with IPv6 TCP on port {}",
port
);
drop(listener);
}
#[test]
fn test_find_target_processes_dedup() {
let listener4 = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener4.local_addr().unwrap().port();
let listener6 = TcpListener::bind(format!("[::1]:{}", port)).unwrap();
let processes = find_target_processes(port).unwrap();
assert_eq!(
processes.len(),
1,
"Expected exactly 1 deduplicated process entry, got {}",
processes.len()
);
drop(listener4);
drop(listener6);
}
#[test]
fn test_find_target_processes_udp6_listener() {
let socket = UdpSocket::bind("[::1]:0").unwrap();
let port = socket.local_addr().unwrap().port();
let processes = find_target_processes(port).unwrap();
assert!(
!processes.is_empty(),
"Expected to find process with IPv6 UDP on port {}",
port
);
drop(socket);
}
}