#![allow(unsafe_code)]
use super::{same_endpoint, SocketPeer};
use std::{
ffi::OsString,
mem::size_of,
net::{Ipv4Addr, Ipv6Addr, SocketAddr},
os::unix::ffi::OsStringExt,
path::PathBuf,
};
const PROC_PIDLISTFDS: i32 = 1;
const PROC_PIDFDSOCKETINFO: i32 = 3;
const PROX_FDTYPE_SOCKET: u32 = 2;
const SOCKINFO_TCP: u32 = 2;
const TCPS_ESTABLISHED: u32 = 4;
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct ProcFdInfo {
fd: i32,
fd_type: u32,
}
#[repr(C, align(8))]
struct SocketInfo([u8; 1024]);
pub(super) fn lookup(local: SocketAddr, peer: SocketAddr) -> Option<SocketPeer> {
let mut holder = None;
for pid in pids()? {
if pid <= 0 {
continue;
}
let Some(fds) = descriptors(pid) else {
continue;
};
for fd in fds {
if fd.fd_type != PROX_FDTYPE_SOCKET {
continue;
}
let mut info = SocketInfo([0; 1024]);
let count = unsafe {
libc::proc_pidfdinfo(
pid,
fd.fd,
PROC_PIDFDSOCKETINFO,
info.0.as_mut_ptr().cast(),
info.0.len() as i32,
)
};
if count < 348 || count as usize > info.0.len() {
continue;
}
if matches_connecting_socket(&info.0, local, peer) {
if holder.is_some() {
return None;
}
holder = Some(pid);
break;
}
}
}
let pid = holder?;
Some(SocketPeer {
pid: pid as u32,
executable: executable(pid)?,
})
}
fn pids() -> Option<Vec<i32>> {
let count = unsafe { libc::proc_listallpids(std::ptr::null_mut(), 0) };
if count <= 0 {
return None;
}
let mut capacity = count as usize + 32;
for _ in 0..4 {
if capacity > 1_000_000 {
return None;
}
let mut pids = vec![0i32; capacity];
let count = unsafe {
libc::proc_listallpids(
pids.as_mut_ptr().cast(),
(pids.len() * size_of::<i32>()) as i32,
)
};
if count <= 0 || count as usize > pids.len() {
return None;
}
if (count as usize) < pids.len() {
pids.truncate(count as usize);
return Some(pids);
}
capacity *= 2;
}
None
}
fn descriptors(pid: i32) -> Option<Vec<ProcFdInfo>> {
let bytes = unsafe { libc::proc_pidinfo(pid, PROC_PIDLISTFDS, 0, std::ptr::null_mut(), 0) };
if bytes <= 0 {
return None;
}
let mut capacity = (bytes as usize).div_ceil(size_of::<ProcFdInfo>()) + 32;
for _ in 0..4 {
if capacity > 1_000_000 {
return None;
}
let mut fds = vec![ProcFdInfo::default(); capacity];
let bytes = unsafe {
libc::proc_pidinfo(
pid,
PROC_PIDLISTFDS,
0,
fds.as_mut_ptr().cast(),
(fds.len() * size_of::<ProcFdInfo>()) as i32,
)
};
if bytes <= 0
|| bytes as usize > fds.len() * size_of::<ProcFdInfo>()
|| !(bytes as usize).is_multiple_of(size_of::<ProcFdInfo>())
{
return None;
}
let count = bytes as usize / size_of::<ProcFdInfo>();
if count < fds.len() {
fds.truncate(count);
return Some(fds);
}
capacity *= 2;
}
None
}
fn matches_connecting_socket(info: &[u8; 1024], local: SocketAddr, peer: SocketAddr) -> bool {
let family = word(info, 184) as i32;
if (family != libc::AF_INET && family != libc::AF_INET6)
|| word(info, 256) != SOCKINFO_TCP
|| word(info, 344) != TCPS_ESTABLISHED
{
return false;
}
let foreign_port = u16::from_be(word(info, 264) as u16);
let local_port = u16::from_be(word(info, 268) as u16);
let (foreign_ip, local_ip) = if info[288] & 1 != 0 {
(
Ipv4Addr::from(<[u8; 4]>::try_from(&info[308..312]).unwrap()).into(),
Ipv4Addr::from(<[u8; 4]>::try_from(&info[324..328]).unwrap()).into(),
)
} else if info[288] & 2 != 0 {
(
Ipv6Addr::from(<[u8; 16]>::try_from(&info[296..312]).unwrap()).into(),
Ipv6Addr::from(<[u8; 16]>::try_from(&info[312..328]).unwrap()).into(),
)
} else {
return false;
};
same_endpoint(SocketAddr::new(local_ip, local_port), peer)
&& same_endpoint(SocketAddr::new(foreign_ip, foreign_port), local)
}
fn word(info: &[u8; 1024], offset: usize) -> u32 {
u32::from_ne_bytes(info[offset..offset + 4].try_into().unwrap())
}
fn executable(pid: i32) -> Option<PathBuf> {
let mut path = vec![0u8; libc::PROC_PIDPATHINFO_MAXSIZE as usize];
let count = unsafe { libc::proc_pidpath(pid, path.as_mut_ptr().cast(), path.len() as u32) };
if count <= 0 || count as usize > path.len() {
return None;
}
let end = path.iter().position(|byte| *byte == 0)?;
if end == 0 {
return None;
}
path.truncate(end);
Some(PathBuf::from(OsString::from_vec(path)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn libproc_descriptor_layout_is_two_32_bit_fields() {
assert_eq!(size_of::<ProcFdInfo>(), 8);
assert_eq!(std::mem::align_of::<SocketInfo>(), 8);
}
}