#[cfg(target_os = "linux")]
pub fn established(pid: u32) -> u32 {
crate::proc_::count_net_established(pid)
}
#[cfg(target_os = "macos")]
pub fn established(pid: u32) -> u32 {
use std::ffi::c_void;
use std::os::raw::{c_int, c_uint};
const PROC_PIDLISTFDS: c_int = 1;
const PROC_PIDFDSOCKETINFO: c_int = 3;
const PROX_FDTYPE_SOCKET: u32 = 2;
const SOCKINFO_TCP: c_int = 2;
const TSI_S_ESTABLISHED: c_int = 4;
const PSI_SIZE: usize = 768;
const PROC_FILEINFO_SIZE: usize = 24;
const SOCKET_FDINFO_SIZE: usize = PROC_FILEINFO_SIZE + PSI_SIZE; const SOI_KIND_OFFSET: usize = 232;
const TCPSI_STATE_OFFSET: usize = 320;
extern "C" {
fn proc_pidinfo(
pid: c_int, flavor: c_int, arg: u64,
buffer: *mut c_void, buffersize: c_int,
) -> c_int;
fn proc_pidfdinfo(
pid: c_int, fd: c_int, flavor: c_int,
buffer: *mut c_void, buffersize: c_int,
) -> c_int;
}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct ProcFdInfo {
proc_fd: c_int,
proc_fdtype: c_uint,
}
#[repr(C)]
struct SocketFdInfo {
_pfi: [u8; PROC_FILEINFO_SIZE],
psi: [u8; PSI_SIZE],
}
const _: [(); SOCKET_FDINFO_SIZE] = [(); std::mem::size_of::<SocketFdInfo>()];
let pid = pid as c_int;
let probe = unsafe { proc_pidinfo(pid, PROC_PIDLISTFDS, 0, std::ptr::null_mut(), 0) };
if probe <= 0 { return 0; }
let needed = (probe as usize).min(4096 * std::mem::size_of::<ProcFdInfo>());
let entry_count = needed / std::mem::size_of::<ProcFdInfo>();
let mut buf: Vec<ProcFdInfo> = vec![ProcFdInfo::default(); entry_count];
let written = unsafe {
proc_pidinfo(
pid, PROC_PIDLISTFDS, 0,
buf.as_mut_ptr() as *mut c_void,
(buf.len() * std::mem::size_of::<ProcFdInfo>()) as c_int,
)
};
if written <= 0 { return 0; }
let got = (written as usize) / std::mem::size_of::<ProcFdInfo>();
let mut count = 0u32;
for fd in buf.iter().take(got) {
if fd.proc_fdtype != PROX_FDTYPE_SOCKET { continue; }
let mut info: Box<SocketFdInfo> = Box::new(unsafe { std::mem::zeroed() });
let n = unsafe {
proc_pidfdinfo(
pid, fd.proc_fd, PROC_PIDFDSOCKETINFO,
info.as_mut() as *mut _ as *mut c_void,
SOCKET_FDINFO_SIZE as c_int,
)
};
if n <= 0 { continue; }
let kind_bytes: &[u8] = &info.psi[SOI_KIND_OFFSET..SOI_KIND_OFFSET + 4];
let soi_kind = i32::from_ne_bytes([kind_bytes[0], kind_bytes[1], kind_bytes[2], kind_bytes[3]]);
if soi_kind != SOCKINFO_TCP { continue; }
let st_bytes: &[u8] = &info.psi[TCPSI_STATE_OFFSET..TCPSI_STATE_OFFSET + 4];
let state = i32::from_ne_bytes([st_bytes[0], st_bytes[1], st_bytes[2], st_bytes[3]]);
if state == TSI_S_ESTABLISHED { count += 1; }
}
count
}
#[cfg(windows)]
pub fn established(pid: u32) -> u32 {
use windows_sys::Win32::NetworkManagement::IpHelper::{
GetExtendedTcpTable, MIB_TCPROW_OWNER_PID, MIB_TCPTABLE_OWNER_PID,
TCP_TABLE_OWNER_PID_CONNECTIONS,
};
use windows_sys::Win32::Networking::WinSock::AF_INET;
const MIB_TCP_STATE_ESTAB: u32 = 5;
let mut buf_size: u32 = 0;
unsafe {
GetExtendedTcpTable(
std::ptr::null_mut(), &mut buf_size, 0, AF_INET as u32,
TCP_TABLE_OWNER_PID_CONNECTIONS, 0,
);
}
if buf_size == 0 || buf_size > 16 * 1024 * 1024 { return 0; }
let mut buf: Vec<u8> = vec![0u8; buf_size as usize];
let rc = unsafe {
GetExtendedTcpTable(
buf.as_mut_ptr() as *mut _, &mut buf_size, 0, AF_INET as u32,
TCP_TABLE_OWNER_PID_CONNECTIONS, 0,
)
};
if rc != 0 { return 0; }
let table = unsafe { &*(buf.as_ptr() as *const MIB_TCPTABLE_OWNER_PID) };
let n = table.dwNumEntries as usize;
let rows: *const MIB_TCPROW_OWNER_PID = &table.table as *const _;
let mut count = 0u32;
for i in 0..n {
let row: &MIB_TCPROW_OWNER_PID = unsafe { &*rows.add(i) };
if row.dwOwningPid == pid && row.dwState == MIB_TCP_STATE_ESTAB {
count += 1;
}
}
count
}
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
#[allow(clashing_extern_declarations)]
pub fn established(pid: u32) -> u32 {
use std::ffi::{c_char, c_int, c_uint, c_void};
const PS_FST_TYPE_SOCKET: c_int = 6;
const KERN_PROC_PID: c_int = 1;
const IPPROTO_TCP: c_int = 6;
const TCPS_ESTABLISHED: c_int = 4;
#[repr(C)]
struct FileStat {
next: *mut c_void,
fs_type: c_int,
fs_flags: c_int,
fs_fflags: c_int,
fs_uflags: c_int,
fs_fd: c_int,
fs_ref_count: c_int,
fs_offset: i64,
fs_typedep: *mut c_void,
fs_path: *mut c_char,
next_stqe_next: *mut FileStat,
}
#[repr(C)]
struct FileStatList { stqh_first: *mut FileStat, stqh_last: *mut *mut FileStat }
#[repr(C)]
struct SockStat {
bytes: [u8; 1024],
}
impl Default for SockStat { fn default() -> Self { Self { bytes: [0; 1024] } } }
extern "C" {
fn procstat_open_sysctl() -> *mut c_void;
fn procstat_close(ps: *mut c_void);
fn procstat_getprocs(ps: *mut c_void, what: c_int, arg: c_int, count: *mut c_uint) -> *mut c_void;
fn procstat_freeprocs(ps: *mut c_void, p: *mut c_void);
fn procstat_getfiles(ps: *mut c_void, kproc: *mut c_void, mmaped: c_int) -> *mut c_void;
fn procstat_freefiles(ps: *mut c_void, head: *mut c_void);
fn procstat_get_socket_info(
ps: *mut c_void, fst: *mut FileStat, ss: *mut SockStat, errbuf: *mut c_char,
) -> c_int;
}
let mut count = 0u32;
unsafe {
let ps = procstat_open_sysctl();
if ps.is_null() { return 0; }
let mut n: c_uint = 0;
let kproc = procstat_getprocs(ps, KERN_PROC_PID, pid as c_int, &mut n);
if kproc.is_null() || n == 0 {
procstat_close(ps); return 0;
}
let head_raw = procstat_getfiles(ps, kproc, 0);
if head_raw.is_null() {
procstat_freeprocs(ps, kproc);
procstat_close(ps); return 0;
}
let head = head_raw as *mut FileStatList;
let mut node = (*head).stqh_first;
let mut walked = 0u32;
while !node.is_null() && walked < 4096 {
walked += 1;
let f = &mut *node;
if f.fs_type == PS_FST_TYPE_SOCKET {
let mut ss = SockStat::default();
let mut errbuf = [0i8; 256];
let rc = procstat_get_socket_info(ps, node, &mut ss, errbuf.as_mut_ptr());
if rc == 0 {
let read_i32 = |off: usize| -> Option<i32> {
if off + 4 > ss.bytes.len() { return None; }
Some(i32::from_ne_bytes([
ss.bytes[off], ss.bytes[off+1],
ss.bytes[off+2], ss.bytes[off+3],
]))
};
let proto = read_i32(36).unwrap_or(0);
let state = read_i32(88).unwrap_or(0);
if proto == IPPROTO_TCP && state == TCPS_ESTABLISHED {
count += 1;
}
}
}
node = f.next_stqe_next;
}
procstat_freefiles(ps, head_raw);
procstat_freeprocs(ps, kproc);
procstat_close(ps);
}
count
}
#[cfg(any(target_os = "openbsd", target_os = "netbsd"))]
pub fn established(_pid: u32) -> u32 {
0
}
#[cfg(not(any(
target_os = "linux", target_os = "macos", windows,
target_os = "freebsd", target_os = "dragonfly",
target_os = "openbsd", target_os = "netbsd",
)))]
pub fn established(_pid: u32) -> u32 { 0 }
#[cfg(test)]
mod tests {
use super::*;
use std::net::{TcpListener, TcpStream};
#[test]
#[cfg(any(target_os = "linux", target_os = "macos", windows))]
fn counts_self_established_tcp() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let _client = TcpStream::connect(addr).unwrap();
let (_server, _) = listener.accept().unwrap();
let pid = std::process::id();
let count = established(pid);
assert!(count >= 2,
"established({}) returned {}, expected >= 2 (listener + client + server in same process)",
pid, count);
}
}