Skip to content

Commit

Permalink
Merge pull request #2 from EphyraSoftware/add-macos
Browse files Browse the repository at this point in the history
Add macos support and extend test coverage
  • Loading branch information
ThetaSinner authored Nov 6, 2024
2 parents 3d74c41 + 4ceedd0 commit 96f8e23
Show file tree
Hide file tree
Showing 8 changed files with 401 additions and 24 deletions.
24 changes: 24 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ doc = false
doctest = false
bench = false

[[bin]]
name = "port-binder-v6"
path = "./sample/port-binder-v6/main.rs"
test = false
doc = false
doctest = false
bench = false

[[bin]]
name = "proc-runner"
path = "./sample/proc-runner/main.rs"
Expand All @@ -22,6 +30,22 @@ doc = false
doctest = false
bench = false

[[bin]]
name = "udp-port-binder"
path = "./sample/udp-port-binder/main.rs"
test = false
doc = false
doctest = false
bench = false

[[bin]]
name = "udp-port-binder-v6"
path = "./sample/udp-port-binder-v6/main.rs"
test = false
doc = false
doctest = false
bench = false

[[bin]]
name = "waiter"
path = "./sample/waiter/main.rs"
Expand Down
7 changes: 7 additions & 0 deletions sample/port-binder-v6/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use std::net::TcpListener;

fn main() {
let listener = TcpListener::bind("[::1]:0").unwrap();
listener.accept().unwrap();
println!("Listener finished");
}
8 changes: 8 additions & 0 deletions sample/udp-port-binder-v6/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use std::net::UdpSocket;

fn main() {
let listener = UdpSocket::bind("[::1]:0").unwrap();
let mut buf = [0; 10];
listener.recv(&mut buf).unwrap();
println!("Done receiving on UDP socket");
}
8 changes: 8 additions & 0 deletions sample/udp-port-binder/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use std::net::UdpSocket;

fn main() {
let listener = UdpSocket::bind("127.0.0.1:0").unwrap();
let mut buf = [0; 10];
listener.recv(&mut buf).unwrap();
println!("Done receiving on UDP socket");
}
14 changes: 12 additions & 2 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
#[cfg(any(target_os = "linux", target_os = "windows", feature = "proc"))]
#[cfg(any(
target_os = "linux",
target_os = "windows",
target_os = "macos",
feature = "proc"
))]
pub(crate) trait MaybeHasPid {
fn get_pid(&self) -> Option<crate::Pid>;
}

#[cfg(any(target_os = "linux", target_os = "windows", feature = "proc"))]
#[cfg(any(
target_os = "linux",
target_os = "windows",
target_os = "macos",
feature = "proc"
))]
pub(crate) fn resolve_pid(maybe_has_pid: &dyn MaybeHasPid) -> crate::ProcCtlResult<crate::Pid> {
match &maybe_has_pid.get_pid() {
Some(pid) => Ok(*pid),
Expand Down
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub enum ProcCtlError {
ProcessError(#[from] procfs::ProcError),

/// An error occurred while searching process information
#[cfg(target_os = "windows")]
#[cfg(any(target_os = "windows", target_os = "macos"))]
#[error("process error")]
ProcessError(String),

Expand Down
244 changes: 234 additions & 10 deletions src/port_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ impl PortQuery {

/// Execute the query
pub fn execute(&self) -> ProcCtlResult<Vec<ProtocolPort>> {
#[cfg(any(target_os = "linux", target_os = "windows"))]
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
let ports = list_ports_for_pid(self, crate::common::resolve_pid(self)?)?;
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
let ports = Vec::with_capacity(0);

if let Some(num) = &self.min_num_ports {
Expand Down Expand Up @@ -146,10 +146,11 @@ fn list_ports_for_pid(query: &PortQuery, pid: Pid) -> ProcCtlResult<Vec<Protocol
let mut out = Vec::new();

if query.tcp_addresses {
let mut tcp_entries = procfs::net::tcp()?;
let mut tcp_entries = proc.tcp()?;

if query.ipv6_addresses {
let tcp6_entries = procfs::net::tcp6()?;
let tcp6_entries = proc.tcp6()?;

tcp_entries.extend(tcp6_entries);
}

Expand All @@ -161,17 +162,15 @@ fn list_ports_for_pid(query: &PortQuery, pid: Pid) -> ProcCtlResult<Vec<Protocol
}

if query.udp_addresses {
let mut udp_entries = procfs::net::udp()?;
let mut udp_entries = proc.udp()?;

if query.ipv6_addresses {
let udp6_entries = procfs::net::udp6()?;
let udp6_entries = proc.udp6()?;
udp_entries.extend(udp6_entries);
}

for entry in udp_entries {
if entry.state == procfs::net::UdpState::Established
&& socket_nodes.contains(&entry.inode)
{
if socket_nodes.contains(&entry.inode) {
out.push(ProtocolPort::Udp(entry.local_address.port()));
}
}
Expand Down Expand Up @@ -324,7 +323,232 @@ fn load_udp_table(
))
}

#[cfg(any(target_os = "linux", target_os = "windows", feature = "proc"))]
#[cfg(target_os = "macos")]
fn list_ports_for_pid(query: &PortQuery, pid: Pid) -> ProcCtlResult<Vec<ProtocolPort>> {
let mut out = Vec::new();

if query.ipv4_addresses {
if query.tcp_addresses {
match std::process::Command::new("lsof")
.arg("-a")
.arg("-iTCP")
.arg("-i4")
.arg("-sTCP:LISTEN")
.arg("-nP")
.arg("-F0pn")
.output()
{
Ok(output) => out.extend(
find_ports_v4(output.stdout.clone(), pid)
.into_iter()
.map(ProtocolPort::Tcp),
),
Err(e) => return Err(ProcCtlError::ProcessError(e.to_string())),
}
}
if query.udp_addresses {
match std::process::Command::new("lsof")
.arg("-a")
.arg("-iUDP")
.arg("-i4")
.arg("-nP")
.arg("-F0pn")
.output()
{
Ok(output) => out.extend(
find_ports_v4(output.stdout.clone(), pid)
.into_iter()
.map(ProtocolPort::Udp),
),
Err(e) => return Err(ProcCtlError::ProcessError(e.to_string())),
}
}
}
if query.ipv6_addresses {
if query.tcp_addresses {
match std::process::Command::new("lsof")
.arg("-a")
.arg("-iTCP")
.arg("-i6")
.arg("-sTCP:LISTEN")
.arg("-nP")
.arg("-F0pn")
.output()
{
Ok(output) => out.extend(
find_ports_v6(output.stdout.clone(), pid)
.into_iter()
.map(ProtocolPort::Tcp),
),
Err(e) => return Err(ProcCtlError::ProcessError(e.to_string())),
}
}
if query.udp_addresses {
match std::process::Command::new("lsof")
.arg("-a")
.arg("-iUDP")
.arg("-i6")
.arg("-nP")
.arg("-F0pn")
.output()
{
Ok(output) => out.extend(
find_ports_v6(output.stdout.clone(), pid)
.into_iter()
.map(ProtocolPort::Udp),
),
Err(e) => return Err(ProcCtlError::ProcessError(e.to_string())),
}
}
}

Ok(out)
}

#[cfg(target_os = "macos")]
fn find_ports_v4(output: Vec<u8>, find_pid: Pid) -> Vec<u16> {
let mut out = Vec::new();

let mut index = 0;
let len = output.len();
while index < len {
if output[index] != b'p' {
break;
}
index += 1;

let start_pid = index;
while index < len && output[index] != 0 {
index += 1;
}

let Some(pid) = String::from_utf8_lossy(&output[start_pid..index])
.parse::<u32>()
.ok()
else {
break;
};
index += 1; // 0
index += 1; // NL

loop {
if pid == find_pid && index < len && output[index] == b'n' {
while index < len && output[index] != b':' {
index += 1;
}
index += 1; // :

let start_port = index;
while index < len && output[index] != 0 {
index += 1;
}

if index >= len {
break;
}

if let Ok(port) = String::from_utf8_lossy(&output[start_port..index]).parse::<u16>()
{
out.push(port);
};
index += 1; // 0
} else {
while index < len && output[index] != 0 {
index += 1;
}
index += 1; // 0
}

if index < len && output[index] == 10 {
// NL
index += 1;
}

if index >= len || output[index] == b'p' {
break;
}
}
}

out
}

#[cfg(target_os = "macos")]
fn find_ports_v6(output: Vec<u8>, find_pid: Pid) -> Vec<u16> {
let mut out = Vec::new();

let mut index = 0;
let len = output.len();
while index < len {
if output[index] != b'p' {
break;
}
index += 1;

let start_pid = index;
while index < len && output[index] != 0 {
index += 1;
}

let Ok(pid) = String::from_utf8_lossy(&output[start_pid..index]).parse::<u32>() else {
break;
};
index += 1; // 0
index += 1; // NL

loop {
if pid == find_pid && index < len && output[index] == b'n' {
while index < len && output[index] != b']' {
index += 1;
}
index += 1; // ]

if index < len && output[index] != b':' {
break;
}
index += 1;

let start_port = index;
while index < len && output[index] != 0 {
index += 1;
}

if index >= len {
break;
}

if let Ok(port) = String::from_utf8_lossy(&output[start_port..index]).parse::<u16>()
{
out.push(port);
};
index += 1; // 0
} else {
while index < len && output[index] != 0 {
index += 1;
}
index += 1; // 0
}

if index < len && output[index] == 10 {
// NL
index += 1;
}

if index >= len || output[index] == b'p' {
break;
}
}
}

out
}

#[cfg(any(
target_os = "linux",
target_os = "windows",
target_os = "macos",
feature = "proc"
))]
impl crate::common::MaybeHasPid for PortQuery {
fn get_pid(&self) -> Option<Pid> {
self.process_id
Expand Down
Loading

0 comments on commit 96f8e23

Please sign in to comment.