diff --git a/leaf/Cargo.toml b/leaf/Cargo.toml index 9677a7f6f..95d2c5b6e 100644 --- a/leaf/Cargo.toml +++ b/leaf/Cargo.toml @@ -233,6 +233,7 @@ warp = { version = "0.3", default-features = false, optional = true } notify = { version = "5.0.0-pre.13", optional = true } # Process +[target.'cfg(any(target_os = "macos", target_os = "linux", target_os="windows"))'.dependencies] netstat2 = "0.9.1" sysinfo = "0.28.4" diff --git a/leaf/src/app/process_finder.rs b/leaf/src/app/process_finder.rs index 23933db49..425b30104 100644 --- a/leaf/src/app/process_finder.rs +++ b/leaf/src/app/process_finder.rs @@ -1,12 +1,23 @@ -use log::debug; -use netstat2::{get_sockets_info, AddressFamilyFlags, ProtocolFlags, ProtocolSocketInfo, TcpState}; -use std::net; use std::net::IpAddr; -use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt}; +use std::sync::{Arc, Mutex}; + +use lazy_static::lazy_static; +use log::debug; +use netstat2::{ + get_sockets_info, AddressFamilyFlags, ProtocolFlags, ProtocolSocketInfo, SocketInfo, +}; +use sysinfo::{Pid, PidExt, ProcessExt, ProcessRefreshKind, System, SystemExt}; + +lazy_static! { + pub static ref SYSTEM: Arc> = { + let system = System::new_all(); + Arc::new(Mutex::new(system)) + }; +} #[derive(Debug)] pub struct PortInfo { - pub address: net::IpAddr, + pub address: IpAddr, pub port: u16, pub protocol: String, pub process_info: Option, @@ -19,14 +30,15 @@ pub struct ProcessInfo { pub process_path: String, } -impl From for PortInfo { - fn from(socket_info: netstat2::SocketInfo) -> Self { +impl From for PortInfo { + fn from(socket_info: SocketInfo) -> Self { let protocol = match socket_info.protocol_socket_info { ProtocolSocketInfo::Tcp(_) => "TCP", ProtocolSocketInfo::Udp(_) => "UDP", }; - let system = System::new_all(); let pid = socket_info.associated_pids.first().unwrap(); + let mut system = SYSTEM.lock().unwrap(); + system.refresh_processes_specifics(ProcessRefreshKind::default()); let process_info = system .process(Pid::from(pid.to_owned() as usize)) @@ -44,8 +56,7 @@ impl From for PortInfo { } } -pub fn find_process(protocol: &str, ip: IpAddr, port: u16) -> Option { - debug!("looking up process for {}:{}:{}", protocol, ip, port); +fn get_socket_info(protocol: &str, ip: &IpAddr, port: u16) -> Option { let mut af_flags: AddressFamilyFlags = AddressFamilyFlags::from_bits(0).unwrap(); if ip.is_ipv6() { af_flags |= AddressFamilyFlags::IPV6; @@ -60,16 +71,28 @@ pub fn find_process(protocol: &str, ip: IpAddr, port: u16) -> Option { if protocol == "tcp" { proto_flags |= ProtocolFlags::TCP; } - let start_time = tokio::time::Instant::now(); let sockets = get_sockets_info(af_flags, proto_flags).unwrap_or_default(); - let mut port_infos = sockets + let socket_info = sockets .into_iter() - .filter(|socket_info| match &socket_info.protocol_socket_info { - ProtocolSocketInfo::Tcp(tcp) => tcp.state != TcpState::Closed, - ProtocolSocketInfo::Udp(_) => true, - }) - .map(|socket_info| PortInfo::from(socket_info)); - let port_info = port_infos.find(|p| p.address == ip && p.port == port); + .find(|p| p.local_addr() == ip.to_owned() && p.local_port() == port); + return socket_info; +} + +pub fn find_process_id(protocol: &str, ip: &IpAddr, port: u16) -> Option { + let start_time = tokio::time::Instant::now(); + let socket_info = get_socket_info(protocol, ip, port); + let pid = socket_info.map(|s| s.associated_pids.first().unwrap().to_owned()); + if let Some(ref pid) = pid { + let elapsed = tokio::time::Instant::now().duration_since(start_time); + debug!("found process id [{}ms] {:?}", elapsed.as_millis(), pid); + } + pid +} + +pub fn find_process(protocol: &str, ip: &IpAddr, port: u16) -> Option { + let start_time = tokio::time::Instant::now(); + let socket_info = get_socket_info(protocol, ip, port); + let port_info = socket_info.map(|socket_info| PortInfo::from(socket_info)); if let Some(ref p) = port_info { let elapsed = tokio::time::Instant::now().duration_since(start_time); debug!("found process [{}ms] {:?}", elapsed.as_millis(), p); diff --git a/leaf/src/app/router.rs b/leaf/src/app/router.rs index c44e66b16..57ab428b6 100644 --- a/leaf/src/app/router.rs +++ b/leaf/src/app/router.rs @@ -9,13 +9,12 @@ use log::*; use maxminddb::geoip2::Country; use memmap2::Mmap; +#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] +use crate::app::process_finder; use crate::app::SyncDnsClient; use crate::config; use crate::session::{Network, Session, SocksAddr}; -#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] -use crate::app::process_finder; - pub trait Condition: Send + Sync + Unpin { fn apply(&self, sess: &Session) -> bool; } @@ -372,17 +371,15 @@ impl ProcessPidMatcher { #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] impl Condition for ProcessPidMatcher { fn apply(&self, sess: &Session) -> bool { - let port_info = process_finder::find_process( + let process_id = process_finder::find_process_id( &sess.network.to_string(), - sess.source.ip(), + &sess.source.ip(), sess.source.port(), ); - if let Some(port) = port_info { - if let Some(process_info) = port.process_info { - if process_info.pid.to_string() == self.value { - debug!("{} matches process id [{}]", process_info.pid, self.value); - return true; - } + if let Some(pid) = process_id { + if pid.to_string() == self.value { + debug!("{} matches process id [{}]", pid, self.value); + return true; } } false @@ -406,7 +403,7 @@ impl Condition for ProcessNameMatcher { fn apply(&self, sess: &Session) -> bool { let port_info = process_finder::find_process( &sess.network.to_string(), - sess.source.ip(), + &sess.source.ip(), sess.source.port(), ); if let Some(port) = port_info { @@ -574,9 +571,11 @@ impl Router { cond_and.add(Box::new(InboundTagMatcher::new(&mut rr.inbound_tags))); } - #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] if rr.processes.len() > 0 { + #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] cond_and.add(Box::new(ProcessMatcher::new(&mut rr.processes))); + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] + warn!("The 'process' rule is not applicable to the current operating system"); } if cond_and.is_empty() {