feat: add freebsd (#71)

* feat: add freebsd
This commit is contained in:
Marco Cadetg
2025-11-02 19:47:26 +01:00
committed by GitHub
parent 03cc04624f
commit 85b2662c85
11 changed files with 597 additions and 23 deletions
+4
View File
@@ -39,6 +39,7 @@ jobs:
- linux-aarch64-gnu
- linux-armv7-gnueabihf
- linux-x64-gnu
- freebsd-x64
- macos-aarch64
- macos-x64
- windows-x64-msvc
@@ -54,6 +55,9 @@ jobs:
cargo: cross
- build: linux-x64-gnu
target: x86_64-unknown-linux-gnu
- build: freebsd-x64
target: x86_64-unknown-freebsd
cargo: cross
- build: macos-aarch64
os: macos-14
target: aarch64-apple-darwin
+4
View File
@@ -58,6 +58,10 @@ windows = { version = "0.62", features = [
"Win32_System_Threading",
] }
# FreeBSD support uses the system's sockstat command for process lookup
# and native libpcap (via pcap crate) for packet capture.
# No additional FreeBSD-specific dependencies required at this time.
[build-dependencies]
anyhow = "1.0"
clap = { version = "4.5", features = ["derive"] }
+96
View File
@@ -8,6 +8,7 @@ This guide covers all installation methods for RustNet across different platform
- [macOS DMG Installation](#macos-dmg-installation)
- [Windows MSI Installation](#windows-msi-installation)
- [Linux Package Installation](#linux-package-installation)
- [FreeBSD Installation](#freebsd-installation)
- [Install via Cargo](#install-via-cargo)
- [Building from Source](#building-from-source)
- [Using Docker](#using-docker)
@@ -203,6 +204,100 @@ sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' $(brew --prefix)/bin/rustnet
rustnet
```
### FreeBSD Installation
FreeBSD support is available starting from version 0.15.0.
#### From Ports or Packages (Future)
Once available in FreeBSD ports:
```bash
# Using pkg (binary packages)
pkg install rustnet
# Or build from ports
cd /usr/ports/net/rustnet && make install clean
```
#### From GitHub Releases
Download the FreeBSD binary from [GitHub Releases](https://github.com/domcyrus/rustnet/releases):
```bash
# Download the appropriate package
fetch https://github.com/domcyrus/rustnet/releases/download/vX.Y.Z/rustnet-vX.Y.Z-x86_64-unknown-freebsd.tar.gz
# Extract the archive
tar xzf rustnet-vX.Y.Z-x86_64-unknown-freebsd.tar.gz
# Move binary to PATH
sudo mv rustnet-vX.Y.Z-x86_64-unknown-freebsd/rustnet /usr/local/bin/
# Make it executable
sudo chmod +x /usr/local/bin/rustnet
# Run with sudo
sudo rustnet
```
#### Building from Source on FreeBSD
```bash
# Install dependencies
pkg install rust libpcap
# Clone the repository
git clone https://github.com/domcyrus/rustnet.git
cd rustnet
# Build in release mode
cargo build --release
# The executable will be in target/release/rustnet
sudo ./target/release/rustnet
```
#### Permission Setup for FreeBSD
FreeBSD requires access to BPF (Berkeley Packet Filter) devices for packet capture.
**Option 1: Run with sudo (Simplest)**
```bash
sudo rustnet
```
**Option 2: Add user to the bpf group (Recommended)**
```bash
# Add your user to the bpf group
sudo pw groupmod bpf -m $(whoami)
# Log out and back in for group changes to take effect
# Now run without sudo
rustnet
```
**Option 3: Change BPF device permissions (Temporary)**
```bash
# This will reset on reboot
sudo chmod o+rw /dev/bpf*
# Now run without sudo
rustnet
```
**Verifying FreeBSD Permissions:**
```bash
# Check if you're in the bpf group
groups | grep bpf
# Check BPF device permissions
ls -la /dev/bpf*
# Test without sudo
rustnet --help
```
## Install via Cargo
```bash
@@ -223,6 +318,7 @@ After installation, see the [Permissions Setup](#permissions-setup) section to c
- libpcap or similar packet capture library:
- **Linux**: `sudo apt-get install libpcap-dev` (Debian/Ubuntu) or `sudo yum install libpcap-devel` (RedHat/CentOS)
- **macOS**: Included by default
- **FreeBSD**: `pkg install libpcap` (included in base system, but headers needed for building)
- **Windows**: Install Npcap and Npcap SDK (see [Windows Build Setup](#windows-build-setup) below)
- **For eBPF support (enabled by default on Linux)**:
- `sudo apt-get install libelf-dev clang llvm` (Debian/Ubuntu)
+1 -1
View File
@@ -19,7 +19,7 @@ A cross-platform network monitoring tool built with Rust. RustNet provides real-
- **Smart Connection Lifecycle**: Protocol-aware timeouts with visual staleness indicators (white → yellow → red) before cleanup
- **Process Identification**: Associate network connections with running processes
- **Service Name Resolution**: Identify well-known services using port numbers
- **Cross-platform Support**: Works on Linux, macOS, Windows and potentially BSD systems
- **Cross-platform Support**: Works on Linux, macOS, Windows, and FreeBSD
- **Advanced Filtering**: Real-time vim/fzf-style filtering with keyword support (`port:`, `src:`, `dst:`, `sni:`, `process:`, `state:`)
- **Terminal User Interface**: Beautiful TUI built with ratatui with adjustable column widths
- **Multi-threaded Processing**: Concurrent packet processing for high performance
+7 -2
View File
@@ -14,7 +14,12 @@ This document outlines the planned features and improvements for RustNet.
- Npcap SDK and runtime integration
- MSI installation packages for 64-bit and 32-bit
- Process identification via Windows IP Helper API (GetExtendedTcpTable/GetExtendedUdpTable)
- [ ] **BSD Support**: Add support for FreeBSD, OpenBSD, and NetBSD
- [x] **FreeBSD Support**: Full support including:
- Process identification via `sockstat` command parsing
- BPF device access and permissions setup
- Native libpcap packet capture
- Cross-compilation support from Linux
- [ ] **OpenBSD and NetBSD Support**: Future platforms to support
- [x] **Linux Process Identification**: **Experimental eBPF Support Implemented** - Basic eBPF-based process identification now available with `--features ebpf`. Provides efficient kernel-level process-to-connection mapping with lower overhead than procfs. Currently has limitations (see eBPF Improvements section below).
## eBPF Improvements (Linux)
@@ -60,7 +65,7 @@ The experimental eBPF support provides efficient process identification but has
- [x] **Connection Lifecycle Management**: Smart protocol-aware timeouts with visual staleness indicators (yellow at 75%, red at 90%)
- [x] **Process Identification**: Associate network connections with running processes (with experimental eBPF support on Linux)
- [x] **Service Name Resolution**: Identify well-known services using port numbers
- [x] **Cross-platform Support**: Works on Linux, macOS, Windows
- [x] **Cross-platform Support**: Works on Linux, macOS, Windows, and FreeBSD
- [ ] **DNS Reverse Lookup**: Add optional hostname resolution (toggle between IP and hostname display)
- [ ] **IPv6 Support**: Full IPv6 connection tracking and display, including DNS resolution (needs testing)
+5
View File
@@ -39,6 +39,11 @@ fn setup_cross_compilation_libs() {
println!("cargo:rustc-link-lib=elf");
println!("cargo:rustc-link-lib=z");
}
"x86_64-unknown-freebsd" => {
// FreeBSD uses libpcap from base system (in /usr/lib)
// When cross-compiling, the sysroot should provide these
println!("cargo:rustc-link-lib=pcap");
}
_ => {
// For other targets, including native builds, let pkg-config handle it
}
+21 -8
View File
@@ -17,7 +17,7 @@ use crate::network::{
capture::{CaptureConfig, PacketReader, setup_packet_capture},
merge::{create_connection_from_packet, merge_packet_into_connection},
parser::{PacketParser, ParsedPacket, ParserConfig},
platform::create_process_lookup_with_pktap_status,
platform::create_process_lookup,
services::ServiceLookup,
types::{ApplicationProtocol, Connection, Protocol},
};
@@ -31,7 +31,12 @@ static QUIC_CONNECTION_MAPPING: LazyLock<Mutex<HashMap<String, String>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
/// Helper function to log connection events as JSON
fn log_connection_event(json_log_path: &str, event_type: &str, conn: &Connection, duration_secs: Option<u64>) {
fn log_connection_event(
json_log_path: &str,
event_type: &str,
conn: &Connection,
duration_secs: Option<u64>,
) {
// Build JSON object based on event type
let mut event = json!({
"timestamp": chrono::Utc::now().to_rfc3339(),
@@ -517,7 +522,12 @@ impl App {
}
// Start the actual process enrichment
if let Err(e) = Self::run_process_enrichment(connections, should_stop, pktap_active, process_detection_method) {
if let Err(e) = Self::run_process_enrichment(
connections,
should_stop,
pktap_active,
process_detection_method,
) {
error!("Process enrichment thread failed: {}", e);
}
});
@@ -533,9 +543,9 @@ impl App {
process_detection_method: Arc<RwLock<String>>,
) -> Result<()> {
// Check PKTAP status before creating process lookup
let is_pktap = pktap_active.load(Ordering::Relaxed);
let use_pktap = pktap_active.load(Ordering::Relaxed);
let process_lookup = create_process_lookup_with_pktap_status(is_pktap)?;
let process_lookup = create_process_lookup(use_pktap)?;
let interval = Duration::from_secs(2); // Use default interval
// Get and set the detection method from the process lookup implementation
@@ -546,8 +556,10 @@ impl App {
*method = process_lookup.get_detection_method().to_string();
}
info!("Process enrichment thread started with detection method: {}",
process_lookup.get_detection_method());
info!(
"Process enrichment thread started with detection method: {}",
process_lookup.get_detection_method()
);
let mut last_refresh = Instant::now();
loop {
@@ -890,7 +902,8 @@ impl App {
&& let Some(dlt) = *linktype_opt
{
// Get interface name to detect TUN/TAP more accurately
let interface_name = self.current_interface
let interface_name = self
.current_interface
.read()
.ok()
.and_then(|opt| opt.clone())
+329
View File
@@ -0,0 +1,329 @@
// network/platform/freebsd.rs - FreeBSD process lookup
use super::{ConnectionKey, ProcessLookup};
use crate::network::types::{Connection, Protocol};
use anyhow::{Context, Result};
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::RwLock;
use std::time::{Duration, Instant};
pub struct FreeBSDProcessLookup {
// Cache: ConnectionKey -> (pid, process_name)
cache: RwLock<ProcessCache>,
}
struct ProcessCache {
lookup: HashMap<ConnectionKey, (u32, String)>,
last_refresh: Instant,
}
impl FreeBSDProcessLookup {
pub fn new() -> Result<Self> {
Ok(Self {
cache: RwLock::new(ProcessCache {
lookup: HashMap::new(),
last_refresh: Instant::now() - Duration::from_secs(3600),
}),
})
}
/// Build connection -> process mapping using sysctl
fn build_process_map() -> Result<HashMap<ConnectionKey, (u32, String)>> {
let mut process_map = HashMap::new();
// Parse TCP connections
if let Ok(tcp_connections) = Self::parse_sockstat_output("tcp") {
process_map.extend(tcp_connections);
}
// Parse TCP6 connections
if let Ok(tcp6_connections) = Self::parse_sockstat_output("tcp6") {
process_map.extend(tcp6_connections);
}
// Parse UDP connections
if let Ok(udp_connections) = Self::parse_sockstat_output("udp") {
process_map.extend(udp_connections);
}
// Parse UDP6 connections
if let Ok(udp6_connections) = Self::parse_sockstat_output("udp6") {
process_map.extend(udp6_connections);
}
Ok(process_map)
}
/// Parse sockstat output for a given protocol
/// Format: user command pid fd proto local_addr foreign_addr
fn parse_sockstat_output(proto: &str) -> Result<HashMap<ConnectionKey, (u32, String)>> {
use std::process::Command;
let mut result = HashMap::new();
// Determine protocol type
let protocol = if proto.starts_with("tcp") {
Protocol::TCP
} else {
Protocol::UDP
};
// Run sockstat command
// -4: IPv4, -6: IPv6, -c: connected sockets, -l: listening sockets, -n: numeric
let ipv6_flag = proto.ends_with('6');
let output = Command::new("sockstat")
.arg(if ipv6_flag { "-6" } else { "-4" })
.arg("-n") // numeric output
.arg("-P")
.arg(if proto.starts_with("tcp") { "tcp" } else { "udp" })
.output()
.context("Failed to execute sockstat")?;
if !output.status.success() {
return Ok(result);
}
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines().skip(1) {
// Skip header
let parts: Vec<&str> = line.split_whitespace().collect();
// Expected format:
// USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS
// root sshd 1234 3 tcp4 192.168.1.1:22 192.168.1.2:54321
if parts.len() < 7 {
continue;
}
// Extract fields
let process_name = parts[1].to_string();
let pid = match parts[2].parse::<u32>() {
Ok(p) => p,
Err(_) => continue,
};
// Parse local address (index 5)
let local_addr = match Self::parse_address(parts[5]) {
Some(addr) => addr,
None => continue,
};
// Parse foreign address (index 6)
let foreign_addr = match Self::parse_address(parts[6]) {
Some(addr) => addr,
None => continue,
};
let key = ConnectionKey {
protocol,
local_addr,
remote_addr: foreign_addr,
};
result.insert(key, (pid, process_name));
}
Ok(result)
}
/// Parse address in format "ip:port", "*:port", or "[ipv6]:port"
fn parse_address(addr_str: &str) -> Option<SocketAddr> {
// Handle wildcard addresses
if addr_str.starts_with("*:") {
let port = addr_str.strip_prefix("*:")?.parse::<u16>().ok()?;
// Use unspecified address for wildcards
return Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port));
}
// Handle IPv6 with brackets: [::1]:8080
if addr_str.starts_with('[') {
let closing_bracket = addr_str.find(']')?;
let ip_str = &addr_str[1..closing_bracket];
let port_str = addr_str.get(closing_bracket + 2..)?; // Skip "]:"
let port = port_str.parse::<u16>().ok()?;
let ip = IpAddr::V6(ip_str.parse().ok()?);
return Some(SocketAddr::new(ip, port));
}
// Split by last colon to handle addresses
let last_colon = addr_str.rfind(':')?;
let (ip_str, port_str) = addr_str.split_at(last_colon);
let port_str = &port_str[1..]; // Remove the colon
let port = port_str.parse::<u16>().ok()?;
// Detect IPv6 (contains colons) vs IPv4
let ip = if ip_str.contains(':') {
// IPv6 address without brackets (e.g., "::1" or "fe80::1")
IpAddr::V6(ip_str.parse().ok()?)
} else {
// IPv4 address
IpAddr::V4(ip_str.parse().ok()?)
};
Some(SocketAddr::new(ip, port))
}
}
impl ProcessLookup for FreeBSDProcessLookup {
fn get_process_for_connection(&self, conn: &Connection) -> Option<(u32, String)> {
let key = ConnectionKey::from_connection(conn);
// Simple cache lookup with no refresh on cache miss.
// The enrichment thread handles periodic refresh.
let cache = self.cache.read().unwrap();
cache.lookup.get(&key).cloned()
}
fn refresh(&self) -> Result<()> {
let process_map = Self::build_process_map()?;
let mut cache = self.cache.write().unwrap();
cache.lookup = process_map;
cache.last_refresh = Instant::now();
Ok(())
}
fn get_detection_method(&self) -> &str {
"sockstat"
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
#[test]
fn test_parse_ipv4_address() {
let addr = FreeBSDProcessLookup::parse_address("192.168.1.1:8080");
assert_eq!(
addr,
Some(SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)),
8080
))
);
}
#[test]
fn test_parse_ipv4_loopback() {
let addr = FreeBSDProcessLookup::parse_address("127.0.0.1:80");
assert_eq!(
addr,
Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 80))
);
}
#[test]
fn test_parse_ipv6_with_brackets() {
let addr = FreeBSDProcessLookup::parse_address("[::1]:8080");
assert_eq!(
addr,
Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080))
);
}
#[test]
fn test_parse_ipv6_full_address_with_brackets() {
let addr = FreeBSDProcessLookup::parse_address("[2001:db8::1]:443");
assert_eq!(
addr,
Some(SocketAddr::new(
IpAddr::V6("2001:db8::1".parse().unwrap()),
443
))
);
}
#[test]
fn test_parse_ipv6_link_local_with_brackets() {
let addr = FreeBSDProcessLookup::parse_address("[fe80::1]:22");
assert_eq!(
addr,
Some(SocketAddr::new(
IpAddr::V6("fe80::1".parse().unwrap()),
22
))
);
}
#[test]
fn test_parse_ipv6_without_brackets() {
// This may occur in some sockstat outputs
let addr = FreeBSDProcessLookup::parse_address("::1:8080");
// This should parse as IPv6 ::1 with port 8080
// Note: This is ambiguous, but our logic treats multiple colons as IPv6
assert!(addr.is_some());
if let Some(socket_addr) = addr {
assert_eq!(socket_addr.port(), 8080);
}
}
#[test]
fn test_parse_wildcard_address() {
let addr = FreeBSDProcessLookup::parse_address("*:80");
assert_eq!(
addr,
Some(SocketAddr::new(
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
80
))
);
}
#[test]
fn test_parse_wildcard_high_port() {
let addr = FreeBSDProcessLookup::parse_address("*:65535");
assert_eq!(
addr,
Some(SocketAddr::new(
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
65535
))
);
}
#[test]
fn test_parse_invalid_address() {
// Missing port
assert_eq!(FreeBSDProcessLookup::parse_address("192.168.1.1"), None);
}
#[test]
fn test_parse_invalid_ipv6_brackets() {
// Missing closing bracket
assert_eq!(FreeBSDProcessLookup::parse_address("[::1:8080"), None);
}
#[test]
fn test_parse_invalid_port() {
// Port out of range
assert_eq!(
FreeBSDProcessLookup::parse_address("192.168.1.1:99999"),
None
);
}
#[test]
fn test_parse_empty_string() {
assert_eq!(FreeBSDProcessLookup::parse_address(""), None);
}
#[test]
fn test_parse_ipv4_mapped_ipv6() {
// IPv4-mapped IPv6 address
let addr = FreeBSDProcessLookup::parse_address("[::ffff:192.168.1.1]:80");
assert_eq!(
addr,
Some(SocketAddr::new(
IpAddr::V6("::ffff:192.168.1.1".parse().unwrap()),
80
))
);
}
}
+18 -5
View File
@@ -4,6 +4,8 @@ use anyhow::Result;
use std::net::SocketAddr;
// Platform-specific modules
#[cfg(target_os = "freebsd")]
mod freebsd;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(all(target_os = "linux", feature = "ebpf"))]
@@ -16,6 +18,8 @@ mod macos;
mod windows;
// Re-export the appropriate implementation
#[cfg(target_os = "freebsd")]
pub use freebsd::FreeBSDProcessLookup;
#[cfg(target_os = "linux")]
pub use linux::LinuxProcessLookup;
#[cfg(target_os = "linux")]
@@ -60,14 +64,12 @@ impl ProcessLookup for NoOpProcessLookup {
}
/// Create a platform-specific process lookup with PKTAP status awareness
pub fn create_process_lookup_with_pktap_status(
_pktap_active: bool,
) -> Result<Box<dyn ProcessLookup>> {
pub fn create_process_lookup(_use_pktap: bool) -> Result<Box<dyn ProcessLookup>> {
#[cfg(target_os = "macos")]
{
use crate::network::platform::macos::MacOSProcessLookup;
if _pktap_active {
if _use_pktap {
log::info!("Using no-op process lookup - PKTAP provides process metadata");
Ok(Box::new(NoOpProcessLookup))
} else {
@@ -104,7 +106,18 @@ pub fn create_process_lookup_with_pktap_status(
Ok(Box::new(WindowsProcessLookup::new()?))
}
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
#[cfg(target_os = "freebsd")]
{
log::info!("Using FreeBSD process lookup (sockstat)");
Ok(Box::new(FreeBSDProcessLookup::new()?))
}
#[cfg(not(any(
target_os = "linux",
target_os = "windows",
target_os = "macos",
target_os = "freebsd"
)))]
{
Err(anyhow::anyhow!("Unsupported platform"))
}
+109 -4
View File
@@ -4,11 +4,16 @@
//! network packets on different platforms (Linux, macOS, Windows).
use anyhow::Result;
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
use anyhow::anyhow;
use log::{debug, info};
#[cfg(any(
not(any(target_os = "linux", target_os = "macos", target_os = "windows")),
not(any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "freebsd"
)),
target_os = "windows"
))]
use log::warn;
@@ -35,7 +40,13 @@ impl PrivilegeStatus {
}
/// Create a status indicating insufficient privileges
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows", test))]
#[cfg(any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "freebsd",
test
))]
pub fn insufficient(missing: Vec<String>, instructions: Vec<String>) -> Self {
Self {
has_privileges: false,
@@ -88,7 +99,17 @@ pub fn check_packet_capture_privileges() -> Result<PrivilegeStatus> {
check_windows_privileges()
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
#[cfg(target_os = "freebsd")]
{
check_freebsd_privileges()
}
#[cfg(not(any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "freebsd"
)))]
{
// Unknown platform - return optimistic result
warn!("Privilege check not implemented for this platform");
@@ -330,6 +351,90 @@ fn check_windows_privileges() -> Result<PrivilegeStatus> {
}
}
#[cfg(target_os = "freebsd")]
fn check_freebsd_privileges() -> Result<PrivilegeStatus> {
use std::fs;
// Check if running as root by reading effective UID from process
let is_root = is_root_user()?;
if is_root {
info!("Running as root - all privileges available");
return Ok(PrivilegeStatus::sufficient());
}
debug!("Not running as root, checking BPF device permissions");
// On FreeBSD, packet capture requires access to BPF devices
// Try to open a BPF device to check permissions
let bpf_devices = (0..10)
.map(|i| format!("/dev/bpf{}", i))
.collect::<Vec<_>>();
let mut can_access_bpf = false;
for bpf_device in &bpf_devices {
if fs::metadata(bpf_device).is_ok() {
debug!("Checking BPF device: {}", bpf_device);
// Try to actually open it (this is the real test)
if std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(bpf_device)
.is_ok()
{
can_access_bpf = true;
debug!("Successfully opened BPF device: {}", bpf_device);
break;
}
}
}
if can_access_bpf {
return Ok(PrivilegeStatus::sufficient());
}
// No BPF access - build error message
let missing = vec!["Access to BPF devices (/dev/bpf*)".to_string()];
let instructions = vec![
"Run with sudo: sudo rustnet".to_string(),
"Add your user to the bpf group:\n \
sudo pw groupmod bpf -m $(whoami)\n \
Then logout and login again"
.to_string(),
"Change BPF device permissions (temporary):\n \
sudo chmod o+rw /dev/bpf*"
.to_string(),
];
Ok(PrivilegeStatus::insufficient(missing, instructions))
}
/// Check if running as root user on FreeBSD
#[cfg(target_os = "freebsd")]
fn is_root_user() -> Result<bool> {
use std::process::Command;
// Use `id -u` command to get effective UID safely
let output = Command::new("id")
.arg("-u")
.output()
.map_err(|e| anyhow!("Failed to run 'id -u': {}", e))?;
if !output.status.success() {
return Err(anyhow!("'id -u' command failed"));
}
let uid_str = String::from_utf8_lossy(&output.stdout);
let uid = uid_str
.trim()
.parse::<u32>()
.map_err(|e| anyhow!("Failed to parse UID: {}", e))?;
Ok(uid == 0)
}
#[cfg(test)]
mod tests {
use super::*;
+3 -3
View File
@@ -2,7 +2,7 @@
#[cfg(target_os = "linux")]
mod linux_tests {
use rustnet_monitor::network::platform::create_process_lookup_with_pktap_status;
use rustnet_monitor::network::platform::create_process_lookup;
#[test]
fn test_process_lookup_creation() {
@@ -31,12 +31,12 @@ mod linux_tests {
#[cfg(target_os = "macos")]
mod other_platforms {
use rustnet_monitor::network::platform::create_process_lookup_with_pktap_status;
use rustnet_monitor::network::platform::create_process_lookup;
#[test]
fn test_other_platform_lookup() {
// Test that other platforms can create process lookups
let result = create_process_lookup_with_pktap_status(false);
let result = create_process_lookup(false);
assert!(result.is_ok(), "Should work on other platforms too");
}
}