8000 Add macOS support and UDP port handling by jkfran · Pull Request #1 · jkfran/killport · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add macOS support and UDP port handling #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Build

on:
pull_request:
branches:
- main

jobs:
build:
strategy:
matrix:
target:
- x86_64-unknown-linux-gnu
- i686-unknown-linux-gnu
- aarch64-unknown-linux-gnu
- armv7-unknown-linux-gnueabihf
- arm-unknown-linux-gnueabihf
- powerpc64le-unknown-linux-gnu
- s390x-unknown-linux-gnu
- aarch64-apple-darwin
- x86_64-apple-darwin
runs-on: ${{ (matrix.target == 'aarch64-apple-darwin' || matrix.target == 'x86_64-apple-darwin') && 'macos-latest' || 'ubuntu-latest' }}
steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
override: true

- name: Build target
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --release --target ${{ matrix.target }}
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ jobs:
- arm-unknown-linux-gnueabihf
- powerpc64le-unknown-linux-gnu
- s390x-unknown-linux-gnu
runs-on: ubuntu-latest
- aarch64-apple-darwin
- x86_64-apple-darwin
runs-on: ${{ (matrix.target == 'aarch64-apple-darwin' || matrix.target == 'x86_64-apple-darwin') && 'macos-latest' || 'ubuntu-latest' }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
Expand Down
10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "killport"
version = "0.1.0"
version = "0.5.0"
authors = ["Francisco Jimenez Cabrera <jkfran@gmail.com>"]
edition = "2021"
license = "MIT"
Expand All @@ -15,10 +15,16 @@ categories = ["command-line-utilities"]
log = "0.4.17"
env_logger = "0.10.0"
clap-verbosity-flag = "2.0.0"
procfs = "0.15.1"
clap = { version = "4.1.8", features = ["derive"] }
nix = "0.26.2"

[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
procfs = "0.15.1"

[target.'cfg(target_os = "macos")'.dependencies]
libproc = "0.13.0"
libc = "0.2"

[dev-dependencies]
assert_cmd = "2.0.10"
tempfile = "3.4.0"
Expand Down
2 changes: 1 addition & 1 deletion snapcraft.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: killport
version: "0.1.0"
version: "0.5.0"
summary: A CLI tool to kill processes using specified ports
description: |
Killport is a command-line utility to find and kill processes listening on specified ports.
Expand Down
70 changes: 64 additions & 6 deletions src/process.rs → src/linux.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
//! This module provides functions for working with processes and inodes.
//!
//! It exposes a single public function `kill_processes_by_inode` that attempts to
//! kill processes associated with a specified inode.

use log::{debug, info, warn};
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
Expand All @@ -11,6 +6,69 @@ use std::io;
use std::io::Error;
use std::path::Path;

/// Attempts to kill processes listening on the specified `port`.
///
/// Returns a `Result` with `true` if any processes were killed, `false` if no
/// processes were found listening on the port, and an `Error` if the operation
/// failed or the platform is unsupported.
///
/// # Arguments
///
/// * `port` - A u16 value representing the port number.
pub fn kill_processes_by_port(port: u16) -> Result<bool, Error> {
let mut killed_any = false;

let target_inodes = find_target_inodes(port);

if !target_inodes.is_empty() {
for target_inode in target_inodes {
killed_any |= kill_processes_by_inode(target_inode)?;
}
}

Ok(killed_any)
}

/// Finds the inodes associated with the specified `port`.
///
/// Returns a `Vec` of inodes for both IPv4 and IPv6 connections.
///
/// # Arguments
///
/// * `port` - A u16 value representing the port number.
#[cfg(target_os = "linux")]
fn find_target_inodes(port: u16) -> Vec<u64> {
let tcp = procfs::net::tcp().unwrap();
let tcp6 = procfs::net::tcp6().unwrap();
let udp = procfs::net::udp().unwrap();
let udp6 = procfs::net::udp6().unwrap();
let mut target_inodes = Vec::new();

target_inodes.extend(
tcp.into_iter()
.filter(|tcp_entry| tcp_entry.local_address.port() == port)
.map(|tcp_entry| tcp_entry.inode),
);
target_inodes.extend(
tcp6.into_iter()
.filter(|tcp_entry| tcp_entry.local_address.port() == port)
.map(|tcp_entry| tcp_entry.inode),
);

target_inodes.extend(
udp.into_iter()
.filter(|udp_entry| udp_entry.local_address.port() == port)
.map(|udp_entry| udp_entry.inode),
);
target_inodes.extend(
udp6.into_iter()
.filter(|udp_entry| udp_entry.local_address.port() == port)
.map(|udp_entry| udp_entry.inode),
);

target_inodes
}

/// Attempts to kill processes associated with the specified `target_inode`.
///
/// Returns a `Result` with `true` if any processes were killed, and an `Error`
Expand All @@ -19,7 +77,7 @@ use std::path::Path;
/// # Arguments
///
/// * `target_inode` - A u64 value representing the target inode.
pub fn kill_processes_by_inode(target_inode: u64) -> Result<bool, Error> {
fn kill_processes_by_inode(target_inode: u64) -> Result<bool, Error> {
let processes = procfs::process::all_processes().unwrap();
let mut killed_any = false;

Expand Down
109 changes: 109 additions & 0 deletions src/macos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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, pidinfo};
use libproc::libproc::task_info::TaskAllInfo;
use libproc::processes::{pids_by_type, ProcFilter};
use log::{debug, info, warn};
use nix::sys::signal::{self, Signal};
use nix::unistd::Pid;
use std::ffi::CStr;
use std::io;

/// Collect information about all processes.
///
/// # Returns
///
/// A vector containing the information of all processes.
fn collect_proc() -> Vec<TaskAllInfo> {
let mut base_procs = Vec::new();

if let Ok(procs) = pids_by_type(ProcFilter::All) {
for p in procs {
if let Ok(task) = pidinfo::<TaskAllInfo>(p as i32, 0) {
base_procs.push(task);
}
}
}

base_procs
}

/// Kill processes listening on the specified port.
///
/// # Arguments
///
/// * `port` - The port number to kill processes listening on.
///
/// # Returns
///
/// A `Result` containing a boolean value. If true, at least one process was killed; otherwise, false.
pub fn kill_processes_by_port(port: u16) -> Result<bool, io::Error> {
let process_infos = collect_proc();
let mut killed = false;

for task in process_infos {
let pid = task.pbsd.pbi_pid as i32;
let mut kill_process = false;

let fds = listpidinfo::<ListFDs>(pid, task.pbsd.pbi_nfiles as usize);
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) {
match socket.psi.soi_kind.into() {
SocketInfoKind::In => {
if socket.psi.soi_protocol == libc::IPPROTO_UDP {
let info = unsafe { socket.psi.soi_proto.pri_in };
let local_port = u16::from_be(info.insi_lport as u16);
if local_port == port {
kill_process = true;
break;
}
}
}
SocketInfoKind::Tcp => {
let info = unsafe { socket.psi.soi_proto.pri_tcp };
let local_port = u16::from_be(info.tcpsi_ini.insi_lport as u16);
if local_port == port {
kill_process = true;
break;
}
}
_ => (),
}
}
}
}
}

if kill_process {
debug!("Found process with PID {}", pid);
let pid = Pid::from_raw(pid);
let cmd = unsafe {
CStr::from_ptr(task.pbsd.pbi_comm.as_ptr())
.to_string_lossy()
.into_owned()
};

if cmd.starts_with("com.docker") {
warn!("Warning: Found Docker. You might need to stop the container manually.");
} else {
info!("Killing process with PID {}", pid);
match signal::kill(pid, Signal::SIGKILL) {
Ok(_) => {
killed = true;
}
Err(e) => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Failed to kill process {}: {}", pid, e),
));
}
}
}
}
}

Ok(killed)
}
20 changes: 15 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
//! The utility accepts a list of port numbers as input and attempts to
//! terminate any processes listening on those ports.

mod port;
mod process;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;

#[cfg(target_os = "linux")]
use linux::kill_processes_by_port;
#[cfg(target_os = "macos")]
use macos::kill_processes_by_port;

use clap::Parser;
use clap_verbosity_flag::{Verbosity, WarnLevel};
use log::error;
use port::kill_port;
use std::process::exit;

/// The `KillPortArgs` struct is used to parse command-line arguments for the
Expand All @@ -19,7 +25,11 @@ use std::process::exit;
#[command(author, version, about, long_about = None)]
struct KillPortArgs {
/// A list of port numbers to kill processes on.
#[arg(name = "ports", help = "The list of port numbers to kill processes on")]
#[arg(
name = "ports",
help = "The list of port numbers to kill processes on",
required = true
)]
ports: Vec<u16>,

/// A verbosity flag to control the level of logging output.
Expand Down Expand Up @@ -51,7 +61,7 @@ fn main() {

// Attempt to kill processes listening on specified ports
for port in args.ports {
match kill_port(port) {
match kill_processes_by_port(port) {
Ok(killed) => {
if killed {
println!("Successfully killed process listening on port {}", port);
Expand Down
63 changes: 0 additions & 63 deletions src/port.rs

This file was deleted.

0