feat: remove CAP_NET_ADMIN and CAP_SYS_ADMIN, use read-only packet capture (#59)

Remove CAP_NET_ADMIN requirement and eliminate need for CAP_SYS_ADMIN on
modern kernels by using non-promiscuous mode for packet capture. This
significantly reduces security surface by following principle of least privilege.
This commit is contained in:
Marco Cadetg
2025-10-19 17:03:58 +02:00
committed by GitHub
parent ff6d924a5d
commit 4ae965a8a4
12 changed files with 216 additions and 182 deletions

View File

@@ -49,7 +49,7 @@ RustNet uses a multi-threaded architecture for efficient packet processing:
Uses libpcap to capture raw packets from the network interface. This thread runs independently and feeds packets into a Crossbeam channel for processing.
**Responsibilities:**
- Open network interface in promiscuous mode
- Open network interface for packet capture (non-promiscuous, read-only mode)
- Apply BPF filters if needed
- Capture raw packets
- Send packets to processing queue
@@ -163,7 +163,7 @@ RustNet uses platform-specific APIs to associate network connections with proces
- Iterates through `/proc/<pid>/fd/` to find socket file descriptors
- Maps inodes to process IDs and resolves process names from `/proc/<pid>/cmdline`
**eBPF Mode (Experimental):**
**eBPF Mode (Default on Linux):**
- Uses kernel eBPF programs attached to socket syscalls
- Captures socket creation events with process context
- Provides lower overhead than procfs scanning
@@ -171,7 +171,10 @@ RustNet uses platform-specific APIs to associate network connections with proces
- Process names limited to 16 characters (kernel `comm` field)
- May show thread names instead of full executable names
- Multi-threaded applications show internal thread names
- Requires `CAP_BPF`, `CAP_PERFMON`, and possibly `CAP_SYS_ADMIN`
- **Capability requirements:**
- Modern Linux (5.8+): `CAP_NET_RAW` (packet capture), `CAP_BPF`, `CAP_PERFMON` (eBPF)
- Legacy Linux (pre-5.8): `CAP_NET_RAW` (packet capture), `CAP_SYS_ADMIN` (eBPF)
- Note: CAP_NET_ADMIN is NOT required (uses read-only, non-promiscuous packet capture)
**Fallback Behavior:**
- If eBPF fails to load (permissions, kernel compatibility), automatically falls back to procfs mode
@@ -319,14 +322,15 @@ RustNet is built with the following key dependencies:
### Privileged Access
RustNet requires privileged access for packet capture:
- **Raw socket access** - Intercept network traffic at low level
- **Promiscuous mode** - Capture all packets on interface (if configured)
- **Raw socket access** - Intercept network traffic at low level (read-only, non-promiscuous mode)
- **BPF device access** - Load packet filters into kernel
- **eBPF programs** - Optional kernel probes for enhanced process tracking (Linux only)
**Mitigation:**
- Use Linux capabilities instead of full root (principle of least privilege)
- Use Linux capabilities instead of full root (CAP_NET_RAW for packet capture, CAP_BPF+CAP_PERFMON for eBPF)
- Use macOS group-based access (`access_bpf` group)
- Audit which users have packet capture permissions
- Operates in read-only mode - cannot modify or inject packets
### Read-Only Operation

View File

@@ -76,7 +76,9 @@ LABEL org.opencontainers.image.description="A cross-platform network monitoring
LABEL org.opencontainers.image.source="https://github.com/domcyrus/rustnet"
LABEL org.opencontainers.image.licenses="Apache License, Version 2.0"
# Important: RustNet requires elevated privileges for packet capture functionality
# Run with: docker run --cap-add=NET_RAW --cap-add=NET_ADMIN rustnet
# Or with: docker run --privileged rustnet
# Important: RustNet requires elevated privileges for packet capture and eBPF functionality
# Modern kernels (5.8+): docker run --cap-add=NET_RAW --cap-add=BPF --cap-add=PERFMON rustnet
# Legacy kernels: docker run --cap-add=NET_RAW --cap-add=SYS_ADMIN rustnet
# Or with: docker run --privileged rustnet
# Note: CAP_NET_ADMIN is NOT required (uses read-only, non-promiscuous packet capture)
ENTRYPOINT ["rustnet"]

View File

@@ -90,13 +90,18 @@ After building (eBPF is enabled by default), test that it works correctly:
sudo cargo run --release
# Option 2: Set capabilities (Linux only, see INSTALL.md Permissions section)
sudo setcap 'cap_net_raw,cap_net_admin,cap_sys_admin,cap_bpf,cap_perfmon+eip' ./target/release/rustnet
# Modern Linux (5.8+):
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' ./target/release/rustnet
./target/release/rustnet
# Legacy Linux (older kernels):
sudo setcap 'cap_net_raw,cap_sys_admin=eip' ./target/release/rustnet
./target/release/rustnet
# Check the TUI Statistics panel to verify it shows "Process Detection: eBPF + procfs"
```
**Note**: eBPF kprobe programs require specific Linux capabilities. See [INSTALL.md - Permissions Setup](INSTALL.md#permissions-setup) for detailed capability requirements. The required capabilities may vary by kernel version.
**Note**: eBPF kprobe programs require specific Linux capabilities. RustNet uses read-only packet capture (CAP_NET_RAW) without promiscuous mode, so CAP_NET_ADMIN is not required. Modern kernels (5.8+) need CAP_BPF and CAP_PERFMON for eBPF, while older kernels require CAP_SYS_ADMIN. See [INSTALL.md - Permissions Setup](INSTALL.md#permissions-setup) for detailed capability requirements.
## Generating vmlinux.h from Your Local Kernel (Optional)
@@ -141,8 +146,9 @@ This is typically not needed since the bundled headers work across kernel versio
**"Permission denied" when loading eBPF**:
- See [INSTALL.md - Permissions Setup](INSTALL.md#permissions-setup) for capability setup
- Required capabilities: `CAP_NET_RAW`, `CAP_NET_ADMIN`, `CAP_BPF`, `CAP_PERFMON`
- Some kernels may also require `CAP_SYS_ADMIN`
- Required capabilities (modern kernel 5.8+): `CAP_NET_RAW`, `CAP_BPF`, `CAP_PERFMON`
- Required capabilities (legacy kernel): `CAP_NET_RAW`, `CAP_SYS_ADMIN`
- Note: CAP_NET_ADMIN is NOT required (RustNet uses read-only packet capture)
**eBPF fails to load, falls back to procfs**:
- This is expected behavior when eBPF can't load

View File

@@ -91,8 +91,8 @@ sudo apt install rustnet
# Run with sudo
sudo rustnet
# Optional: Grant capabilities to run without sudo (see Permissions section)
sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/rustnet
# Optional: Grant capabilities to run without sudo (modern kernel 5.8+)
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' /usr/bin/rustnet
rustnet
```
@@ -175,8 +175,8 @@ sudo dnf install rustnet
# Run with sudo
sudo rustnet
# Optional: Grant capabilities to run without sudo (see Permissions section)
sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/rustnet
# Optional: Grant capabilities to run without sudo (modern kernel 5.8+)
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' /usr/bin/rustnet
rustnet
```
@@ -196,8 +196,8 @@ brew install rustnet
```bash
brew install domcyrus/rustnet/rustnet
# Grant capabilities to the Homebrew-installed binary
sudo setcap cap_net_raw,cap_net_admin=eip $(brew --prefix)/bin/rustnet
# Grant capabilities to the Homebrew-installed binary (modern kernel 5.8+)
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' $(brew --prefix)/bin/rustnet
# Run without sudo
rustnet
@@ -299,16 +299,16 @@ docker pull ghcr.io/domcyrus/rustnet:latest
# Or pull a specific version
docker pull ghcr.io/domcyrus/rustnet:0.7.0
# Run with required network capabilities (latest)
docker run --rm -it --cap-add=NET_RAW --cap-add=NET_ADMIN --net=host \
# Run with required capabilities for eBPF support (latest)
docker run --rm -it --cap-add=NET_RAW --cap-add=BPF --cap-add=PERFMON --net=host \
ghcr.io/domcyrus/rustnet:latest
# Run with specific version
docker run --rm -it --cap-add=NET_RAW --cap-add=NET_ADMIN --net=host \
docker run --rm -it --cap-add=NET_RAW --cap-add=BPF --cap-add=PERFMON --net=host \
ghcr.io/domcyrus/rustnet:0.7.0
# Run with specific interface
docker run --rm -it --cap-add=NET_RAW --cap-add=NET_ADMIN --net=host \
docker run --rm -it --cap-add=NET_RAW --cap-add=BPF --cap-add=PERFMON --net=host \
ghcr.io/domcyrus/rustnet:latest -i eth0
# Alternative: Run with privileged mode (less secure but simpler)
@@ -319,18 +319,30 @@ docker run --rm -it --privileged --net=host \
docker run --rm ghcr.io/domcyrus/rustnet:latest --help
```
**Note:** The container requires network capabilities (`NET_RAW` and `NET_ADMIN`) or privileged mode for packet capture. Host networking (`--net=host`) is recommended for monitoring all network interfaces.
**Note:** The container requires capabilities (`NET_RAW`, `BPF`, and `PERFMON`) or privileged mode for packet capture with eBPF support. Host networking (`--net=host`) is recommended for monitoring all network interfaces.
## Permissions Setup
RustNet requires elevated privileges to capture network packets because accessing network interfaces for packet capture is a privileged operation on all modern operating systems. This section explains how to properly grant these permissions on different platforms.
> ### **Security Advantage: Read-Only Network Access on Linux**
>
> **RustNet uses read-only packet capture without promiscuous mode on all platforms.** This means:
>
> **Linux:** Requires only **`CAP_NET_RAW`** capability - **NOT** full root or `CAP_NET_ADMIN`
> **Principle of Least Privilege:** Minimal permissions needed for packet capture
> **No Promiscuous Mode:** Only captures packets to/from the host (not all network traffic)
> **Read-Only:** Cannot modify or inject packets
> **Enhanced Security:** Reduced attack surface compared to full root access
>
> **macOS Note:** PKTAP (for process metadata) requires root privileges, but you can run without sudo using the `lsof` fallback for basic packet capture.
### Why Permissions Are Required
Network packet capture requires access to:
- **Raw sockets** for low-level network access
- **Network interfaces** in promiscuous mode
- **Raw sockets** for low-level network access (read-only, non-promiscuous mode)
- **Network interfaces** for packet capture
- **BPF (Berkeley Packet Filter) devices** on macOS/BSD systems
- **Network namespaces** on some Linux configurations
@@ -340,6 +352,8 @@ These capabilities are restricted to prevent malicious software from interceptin
On macOS, packet capture requires access to BPF (Berkeley Packet Filter) devices located at `/dev/bpf*`.
**Note:** macOS PKTAP (for extracting process metadata from packets) requires **root/sudo** privileges. Without sudo, RustNet uses `lsof` as a fallback for process detection (slower but works without root).
#### Option 1: Run with sudo (Simplest)
```bash
@@ -394,9 +408,11 @@ brew install rustnet
# Follow the caveats displayed after installation
```
### Linux Permission Setup
### Linux Permission Setup (Read-Only Access - No Root Required!)
On Linux, packet capture requires `CAP_NET_RAW` and `CAP_NET_ADMIN` capabilities.
**Linux Advantage:** RustNet requires **only `CAP_NET_RAW`** for packet capture - far less than full root access!
On Linux, packet capture requires only the `CAP_NET_RAW` capability for read-only, non-promiscuous packet capture. For eBPF-enhanced process tracking, additional capabilities (`CAP_BPF` and `CAP_PERFMON`) are needed, but **`CAP_NET_ADMIN` is NOT required**.
#### Option 1: Run with sudo (Simplest)
@@ -416,8 +432,8 @@ Grant specific network capabilities to the binary without full root privileges:
# Build the binary first
cargo build --release
# Grant network capabilities to the binary
sudo setcap cap_net_raw,cap_net_admin=eip ./target/release/rustnet
# Grant capabilities to the binary (modern kernel 5.8+, with eBPF support)
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' ./target/release/rustnet
# Now run without sudo
./target/release/rustnet
@@ -426,8 +442,8 @@ sudo setcap cap_net_raw,cap_net_admin=eip ./target/release/rustnet
**For cargo-installed binaries:**
```bash
# If installed via cargo install rustnet-monitor
sudo setcap cap_net_raw,cap_net_admin=eip ~/.cargo/bin/rustnet
# If installed via cargo install rustnet-monitor (modern kernel 5.8+)
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' ~/.cargo/bin/rustnet
# Now run without sudo
rustnet
@@ -441,28 +457,32 @@ eBPF is enabled by default on Linux and provides lower-overhead process identifi
# Build in release mode (eBPF is enabled by default)
cargo build --release
# Try modern capabilities first (Linux 5.8+)
sudo setcap 'cap_net_raw,cap_net_admin,cap_bpf,cap_perfmon+eip' ./target/release/rustnet
# Modern Linux (5.8+) - works with just these three capabilities:
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' ./target/release/rustnet
./target/release/rustnet
# If eBPF fails to load, add CAP_SYS_ADMIN (may be required depending on kernel version)
sudo setcap 'cap_net_raw,cap_net_admin,cap_sys_admin,cap_bpf,cap_perfmon+eip' ./target/release/rustnet
# Legacy Linux (older kernels without CAP_BPF) - use CAP_SYS_ADMIN as fallback:
sudo setcap 'cap_net_raw,cap_sys_admin=eip' ./target/release/rustnet
./target/release/rustnet
# Check TUI Statistics panel - should show "Process Detection: eBPF + procfs"
```
**Capability requirements for eBPF:**
**Capability requirements:**
Base capabilities (always required):
- `CAP_NET_RAW` - Raw socket access for packet capture
- `CAP_NET_ADMIN` - Network administration
**Base capability (always required):**
- `CAP_NET_RAW` - Raw socket access for read-only packet capture (non-promiscuous mode)
eBPF-specific capabilities (Linux 5.8+):
**eBPF-specific capabilities (choose based on kernel version):**
**Modern Linux (5.8+):**
- `CAP_BPF` - BPF program loading and map operations
- `CAP_PERFMON` - Performance monitoring and tracing operations
Additional capability (may be required):
- `CAP_SYS_ADMIN` - Some kernel versions or configurations may still require this for kprobe attachment, even with CAP_BPF and CAP_PERFMON available. Requirements vary by kernel version and configuration.
**Legacy Linux (pre-5.8):**
- `CAP_SYS_ADMIN` - Required for BPF operations on older kernels without CAP_BPF support
**Note:** CAP_NET_ADMIN is NOT required. RustNet uses read-only packet capture without promiscuous mode.
**Fallback behavior**: If eBPF cannot load (e.g., insufficient capabilities, incompatible kernel), the application automatically uses procfs-only mode. The TUI Statistics panel displays which detection method is active:
- `Process Detection: eBPF + procfs` - eBPF successfully loaded
@@ -473,8 +493,8 @@ Additional capability (may be required):
**For system-wide installation:**
```bash
# If installed via package manager or copied to /usr/local/bin
sudo setcap cap_net_raw,cap_net_admin=eip /usr/local/bin/rustnet
# If installed via package manager or copied to /usr/local/bin (modern kernel 5.8+)
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' /usr/local/bin/rustnet
rustnet
```
@@ -516,7 +536,8 @@ getcap ~/.cargo/bin/rustnet
# For system-wide installations:
getcap $(which rustnet)
# Should show: cap_net_raw,cap_net_admin=eip
# Modern (5.8+): Should show cap_net_raw,cap_bpf,cap_perfmon=eip
# Legacy: Should show cap_net_raw,cap_sys_admin=eip
# Test without sudo
rustnet --help
@@ -552,7 +573,7 @@ rustnet --help
#### Operation Not Permitted (with capabilities set)
- Capabilities may have been removed by system updates
- Re-apply capabilities: `sudo setcap cap_net_raw,cap_net_admin=eip $(which rustnet)`
- Re-apply capabilities (modern): `sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' $(which rustnet)`
- Some filesystems don't support extended attributes (capabilities)
- Try copying the binary to a different filesystem (e.g., from NFS to local disk)

View File

@@ -37,7 +37,7 @@ We initially attempted to include eBPF support, which required vendoring libelf
For users on older distributions, the `cargo install` workaround is documented:
```bash
cargo install rustnet-monitor
sudo setcap cap_net_raw,cap_net_admin=eip ~/.cargo/bin/rustnet
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' ~/.cargo/bin/rustnet
```
## Potential Future Approaches

View File

@@ -90,7 +90,7 @@ cargo build --release
**Via Docker:**
```bash
docker pull ghcr.io/domcyrus/rustnet:latest
docker run --rm -it --cap-add=NET_RAW --cap-add=NET_ADMIN --net=host \
docker run --rm -it --cap-add=NET_RAW --cap-add=BPF --cap-add=PERFMON --net=host \
ghcr.io/domcyrus/rustnet:latest
```
@@ -98,13 +98,19 @@ docker run --rm -it --cap-add=NET_RAW --cap-add=NET_ADMIN --net=host \
Packet capture requires elevated privileges. See [INSTALL.md](INSTALL.md) for detailed permission setup.
> **Security Note (Linux):** RustNet uses **read-only packet capture** without promiscuous mode, requiring only `CAP_NET_RAW` (not full root or `CAP_NET_ADMIN`). This follows the principle of least privilege for enhanced security.
```bash
# Quick start with sudo
# Quick start with sudo (all platforms)
sudo rustnet
# Or grant capabilities to run without sudo (Linux)
sudo setcap cap_net_raw,cap_net_admin=eip /path/to/rustnet
# Linux: Grant minimal capabilities (recommended - no root required!)
# Read-only packet capture + eBPF process tracking
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' /path/to/rustnet
rustnet
# macOS: PKTAP requires root for process metadata
sudo rustnet # or use 'lsof' fallback without sudo
```
**Common options:**

View File

@@ -23,8 +23,8 @@ Packet capture requires elevated privileges on most systems. See [INSTALL.md](IN
sudo rustnet
# Or grant capabilities to run without sudo (see INSTALL.md for details)
# Linux example:
sudo setcap cap_net_raw,cap_net_admin=eip /path/to/rustnet
# Linux example (modern kernel 5.8+):
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' /path/to/rustnet
rustnet
```

28
debian/postinst vendored
View File

@@ -9,12 +9,11 @@ case "$1" in
# This allows rustnet to run as a normal user with enhanced eBPF process detection
if command -v setcap >/dev/null 2>&1; then
# Try modern capabilities first (Linux 5.8+)
# CAP_NET_RAW, CAP_NET_ADMIN: packet capture
# CAP_BPF, CAP_PERFMON: eBPF support
# CAP_SYS_ADMIN: may be required for kprobe attachment on some kernel versions
setcap 'cap_net_raw,cap_net_admin,cap_sys_admin,cap_bpf,cap_perfmon+eip' /usr/bin/rustnet 2>/dev/null || \
# CAP_NET_RAW: read-only packet capture (non-promiscuous mode)
# CAP_BPF, CAP_PERFMON: eBPF support for enhanced process tracking
setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/bin/rustnet 2>/dev/null || \
# Fallback for older kernels without CAP_BPF/CAP_PERFMON
setcap 'cap_net_raw,cap_net_admin,cap_sys_admin+eip' /usr/bin/rustnet || true
setcap 'cap_net_raw,cap_sys_admin+eip' /usr/bin/rustnet || true
fi
cat <<EOF
@@ -29,18 +28,21 @@ NETWORK PACKET CAPTURE PERMISSIONS:
To verify permissions are set correctly:
getcap /usr/bin/rustnet
Expected output (Linux 5.8+):
/usr/bin/rustnet cap_net_raw,cap_net_admin,cap_sys_admin,cap_bpf,cap_perfmon=eip
Expected output (modern Linux 5.8+):
/usr/bin/rustnet cap_net_raw,cap_bpf,cap_perfmon=eip
Or for older kernels:
/usr/bin/rustnet cap_net_raw,cap_net_admin,cap_sys_admin=eip
Or for legacy kernels (pre-5.8):
/usr/bin/rustnet cap_net_raw,cap_sys_admin=eip
If capabilities are not set, you can manually set them:
# For Linux 5.8+ with eBPF support
sudo setcap 'cap_net_raw,cap_net_admin,cap_sys_admin,cap_bpf,cap_perfmon+eip' /usr/bin/rustnet
# For modern Linux 5.8+ with eBPF support
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' /usr/bin/rustnet
# Or for older kernels
sudo setcap 'cap_net_raw,cap_net_admin,cap_sys_admin+eip' /usr/bin/rustnet
# Or for legacy kernels without CAP_BPF support
sudo setcap 'cap_net_raw,cap_sys_admin+eip' /usr/bin/rustnet
Note: RustNet uses read-only packet capture (no promiscuous mode).
CAP_NET_ADMIN is NOT required.
Alternatively, run rustnet with sudo:
sudo rustnet

View File

@@ -62,12 +62,11 @@ install -Dpm 0644 resources/packaging/linux/rustnet.desktop -t %{buildroot}%{_da
# This allows rustnet to run as a normal user with enhanced eBPF process detection
if command -v setcap >/dev/null 2>&1; then
# Try modern capabilities first (Linux 5.8+)
# CAP_NET_RAW, CAP_NET_ADMIN: packet capture
# CAP_BPF, CAP_PERFMON: eBPF support
# CAP_SYS_ADMIN: may be required for kprobe attachment on some kernel versions
setcap 'cap_net_raw,cap_net_admin,cap_sys_admin,cap_bpf,cap_perfmon+eip' %{_bindir}/rustnet 2>/dev/null || \
# CAP_NET_RAW: read-only packet capture (non-promiscuous mode)
# CAP_BPF, CAP_PERFMON: eBPF support for enhanced process tracking
setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' %{_bindir}/rustnet 2>/dev/null || \
# Fallback for older kernels without CAP_BPF/CAP_PERFMON
setcap 'cap_net_raw,cap_net_admin,cap_sys_admin+eip' %{_bindir}/rustnet || :
setcap 'cap_net_raw,cap_sys_admin+eip' %{_bindir}/rustnet || :
fi
%posttrans
@@ -83,18 +82,21 @@ NETWORK PACKET CAPTURE PERMISSIONS:
To verify permissions are set correctly:
getcap %{_bindir}/rustnet
Expected output (Linux 5.8+):
%{_bindir}/rustnet cap_net_raw,cap_net_admin,cap_sys_admin,cap_bpf,cap_perfmon=eip
Expected output (modern Linux 5.8+):
%{_bindir}/rustnet cap_net_raw,cap_bpf,cap_perfmon=eip
Or for older kernels:
%{_bindir}/rustnet cap_net_raw,cap_net_admin,cap_sys_admin=eip
Or for legacy kernels (pre-5.8):
%{_bindir}/rustnet cap_net_raw,cap_sys_admin=eip
If capabilities are not set, you can manually set them:
# For Linux 5.8+ with eBPF support
sudo setcap 'cap_net_raw,cap_net_admin,cap_sys_admin,cap_bpf,cap_perfmon+eip' %{_bindir}/rustnet
# For modern Linux 5.8+ with eBPF support
sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon+eip' %{_bindir}/rustnet
# Or for older kernels
sudo setcap 'cap_net_raw,cap_net_admin,cap_sys_admin+eip' %{_bindir}/rustnet
# Or for legacy kernels without CAP_BPF support
sudo setcap 'cap_net_raw,cap_sys_admin+eip' %{_bindir}/rustnet
Note: RustNet uses read-only packet capture (no promiscuous mode).
CAP_NET_ADMIN is NOT required.
Alternatively, run rustnet with sudo:
sudo rustnet

View File

@@ -7,8 +7,6 @@ use pcap::{Active, Capture, Device, Error as PcapError};
pub struct CaptureConfig {
/// Network interface name (None for default)
pub interface: Option<String>,
/// Promiscuous mode
pub promiscuous: bool,
/// Snapshot length (bytes to capture per packet)
pub snaplen: i32,
/// Buffer size for packet capture
@@ -23,7 +21,6 @@ impl Default for CaptureConfig {
fn default() -> Self {
Self {
interface: None,
promiscuous: true,
snaplen: 1514, // Limit packet size to keep more in buffer
buffer_size: 20_000_000, // 20MB buffer
timeout_ms: 150, // 150ms timeout for UI responsiveness
@@ -34,8 +31,12 @@ impl Default for CaptureConfig {
/// Find the best active network device
fn find_best_device() -> Result<Device> {
let devices = Device::list()
.map_err(|e| anyhow!("Failed to list network devices: {}. This may indicate insufficient privileges.", e))?;
let devices = Device::list().map_err(|e| {
anyhow!(
"Failed to list network devices: {}. This may indicate insufficient privileges.",
e
)
})?;
log::info!(
"Scanning {} devices for best active interface...",
@@ -72,7 +73,11 @@ fn find_best_device() -> Result<Device> {
// First priority: up, running, has a valid IP address, and NOT virtual
.find(|d| {
// Check if it's a virtual/problematic interface
let desc_lower = d.desc.as_ref().map(|s| s.to_lowercase()).unwrap_or_default();
let desc_lower = d
.desc
.as_ref()
.map(|s| s.to_lowercase())
.unwrap_or_default();
let is_virtual = desc_lower.contains("hyper-v")
|| desc_lower.contains("vmware")
|| desc_lower.contains("virtualbox");
@@ -105,7 +110,11 @@ fn find_best_device() -> Result<Device> {
.or_else(|| {
devices.iter().find(|d| {
// Check if it's a virtual/problematic interface
let desc_lower = d.desc.as_ref().map(|s| s.to_lowercase()).unwrap_or_default();
let desc_lower = d
.desc
.as_ref()
.map(|s| s.to_lowercase())
.unwrap_or_default();
let is_virtual = desc_lower.contains("hyper-v")
|| desc_lower.contains("virtual")
|| desc_lower.contains("vmware")
@@ -198,14 +207,20 @@ pub fn setup_packet_capture(config: CaptureConfig) -> Result<(Capture<Active>, S
}
Err(e) => {
log::warn!("Failed to open PKTAP capture: {}", e);
log::info!("PKTAP requires root privileges - run with 'sudo' for process metadata support");
log::info!("Falling back to regular capture (process detection will use lsof)");
log::info!(
"PKTAP requires root privileges - run with 'sudo' for process metadata support"
);
log::info!(
"Falling back to regular capture (process detection will use lsof)"
);
}
}
}
Err(e) => {
log::warn!("Failed to create PKTAP device: {}", e);
log::info!("PKTAP requires root privileges - run with 'sudo' for process metadata support");
log::info!(
"PKTAP requires root privileges - run with 'sudo' for process metadata support"
);
log::info!("Falling back to regular capture (process detection will use lsof)");
}
}
@@ -239,18 +254,10 @@ pub fn setup_packet_capture(config: CaptureConfig) -> Result<(Capture<Active>, S
let device_name = device.name.clone();
// Disable promiscuous mode for "any" interface on Linux
// The "any" device doesn't support promiscuous mode
let use_promisc = if device_name == "any" {
log::info!("Disabling promiscuous mode for 'any' interface (not supported)");
false
} else {
config.promiscuous
};
// Create capture handle
// Create capture handle with promiscuous mode disabled
// We use non-promiscuous mode (read-only packet capture) which only requires CAP_NET_RAW
let cap = Capture::from_device(device)?
.promisc(use_promisc)
.promisc(false)
.snaplen(config.snaplen)
.buffer_size(config.buffer_size)
.timeout(config.timeout_ms)
@@ -319,10 +326,7 @@ fn find_capture_device(interface_name: &Option<String>) -> Result<Device> {
}
// List available interfaces for error message
let available: Vec<String> = devices
.iter()
.map(|d| d.name.clone())
.collect();
let available: Vec<String> = devices.iter().map(|d| d.name.clone()).collect();
Err(anyhow!(
"Interface '{}' not found. Available interfaces: {}",
@@ -460,7 +464,6 @@ mod tests {
#[test]
fn test_default_config() {
let config = CaptureConfig::default();
assert!(config.promiscuous);
assert_eq!(config.snaplen, 1514);
assert!(config.filter.is_none()); // Default starts without filter
}

View File

@@ -111,59 +111,60 @@ impl EbpfLoader {
{
debug!("eBPF: Current effective capabilities: 0x{:x}", cap_value);
// Required capabilities (bit positions):
// CAP_NET_RAW = 13, CAP_NET_ADMIN = 12, CAP_BPF = 39, CAP_PERFMON = 38, CAP_SYS_ADMIN = 21
let required_caps = [
13, // CAP_NET_RAW - for packet capture
12, // CAP_NET_ADMIN - for network administration
21, // CAP_SYS_ADMIN - fallback for older kernels
];
// Capability bit positions
const CAP_NET_RAW: u64 = 13;
const CAP_SYS_ADMIN: u64 = 21;
const CAP_BPF: u64 = 39;
const CAP_PERFMON: u64 = 38;
// Optional modern capabilities (Linux 5.8+)
let modern_caps = [
39, // CAP_BPF - for BPF operations
38, // CAP_PERFMON - for performance monitoring
];
// Check CAP_NET_RAW (required for read-only packet capture)
let has_net_raw = (cap_value & (1u64 << CAP_NET_RAW)) != 0;
// Check required capabilities
let has_required = required_caps.iter().all(|&cap| {
let has_cap = (cap_value & (1u64 << cap)) != 0;
debug!(
"eBPF: Capability CAP_NET_RAW (bit {}): {}",
CAP_NET_RAW,
if has_net_raw { "present" } else { "missing" }
);
// Must have CAP_NET_RAW for packet capture
if !has_net_raw {
debug!("eBPF: Missing CAP_NET_RAW (required for packet capture)");
debug!(
"eBPF: Capability {} (bit {}): {}",
match cap {
13 => "CAP_NET_RAW",
12 => "CAP_NET_ADMIN",
21 => "CAP_SYS_ADMIN",
_ => "UNKNOWN",
},
cap,
if has_cap { "present" } else { "missing" }
"eBPF: Insufficient capabilities - need CAP_NET_RAW for packet capture, plus either (CAP_BPF+CAP_PERFMON) or CAP_SYS_ADMIN for eBPF"
);
has_cap
});
return false;
}
// Check modern capabilities (nice to have)
let has_modern = modern_caps.iter().any(|&cap| {
let has_cap = (cap_value & (1u64 << cap)) != 0;
debug!(
"eBPF: Modern capability {} (bit {}): {}",
match cap {
39 => "CAP_BPF",
38 => "CAP_PERFMON",
_ => "UNKNOWN",
},
cap,
if has_cap { "present" } else { "missing" }
);
has_cap
});
// Check modern capabilities (Linux 5.8+)
let has_bpf = (cap_value & (1u64 << CAP_BPF)) != 0;
let has_perfmon = (cap_value & (1u64 << CAP_PERFMON)) != 0;
if has_required {
if has_modern {
info!("eBPF: All required and modern capabilities present");
} else {
info!("eBPF: Required capabilities present, using legacy mode");
}
debug!(
"eBPF: Modern capability CAP_BPF (bit {}): {}",
CAP_BPF,
if has_bpf { "present" } else { "missing" }
);
debug!(
"eBPF: Modern capability CAP_PERFMON (bit {}): {}",
CAP_PERFMON,
if has_perfmon { "present" } else { "missing" }
);
// Check legacy capability
let has_sys_admin = (cap_value & (1u64 << CAP_SYS_ADMIN)) != 0;
debug!(
"eBPF: Capability CAP_SYS_ADMIN (bit {}): {}",
CAP_SYS_ADMIN,
if has_sys_admin { "present" } else { "missing" }
);
// Accept either modern capabilities OR legacy capability
if has_bpf && has_perfmon {
info!("eBPF: Using modern capabilities (CAP_BPF + CAP_PERFMON)");
return true;
} else if has_sys_admin {
info!("eBPF: Using legacy capability (CAP_SYS_ADMIN)");
return true;
} else {
debug!("eBPF: Missing required capabilities");
@@ -172,7 +173,7 @@ impl EbpfLoader {
}
debug!(
"eBPF: Insufficient capabilities - need CAP_NET_RAW, CAP_NET_ADMIN, and CAP_SYS_ADMIN (or CAP_BPF+CAP_PERFMON on newer kernels)"
"eBPF: Insufficient capabilities - need CAP_NET_RAW for packet capture, plus either (CAP_BPF+CAP_PERFMON) or CAP_SYS_ADMIN for eBPF"
);
false
}

View File

@@ -9,7 +9,6 @@ use anyhow::anyhow;
use log::{debug, info};
#[cfg(any(
not(any(target_os = "linux", target_os = "macos", target_os = "windows")),
target_os = "linux",
target_os = "windows"
))]
use log::warn;
@@ -125,51 +124,33 @@ fn check_linux_privileges() -> Result<PrivilegeStatus> {
debug!("Current effective capabilities: 0x{:x}", cap_value);
// Required capabilities for packet capture
// Required capability for read-only packet capture (no promiscuous mode)
const CAP_NET_RAW: u64 = 13; // For packet capture
const CAP_NET_ADMIN: u64 = 12; // For network administration
let mut missing = Vec::new();
let mut has_net_raw = false;
let mut has_net_admin = false;
// Check CAP_NET_RAW
if (cap_value & (1u64 << CAP_NET_RAW)) != 0 {
debug!("CAP_NET_RAW: present");
has_net_raw = true;
return Ok(PrivilegeStatus::sufficient());
} else {
debug!("CAP_NET_RAW: missing");
missing.push("CAP_NET_RAW capability".to_string());
}
// Check CAP_NET_ADMIN
if (cap_value & (1u64 << CAP_NET_ADMIN)) != 0 {
debug!("CAP_NET_ADMIN: present");
has_net_admin = true;
} else {
debug!("CAP_NET_ADMIN: missing");
missing.push("CAP_NET_ADMIN capability".to_string());
}
// Need at least CAP_NET_RAW for basic packet capture
if has_net_raw {
if !has_net_admin {
warn!("CAP_NET_ADMIN missing - some features may not work");
}
return Ok(PrivilegeStatus::sufficient());
missing.push("CAP_NET_RAW capability (required for packet capture)".to_string());
}
// Build instructions for gaining privileges
let mut instructions = vec![
"Run with sudo: sudo rustnet".to_string(),
"Set capabilities: sudo setcap cap_net_raw,cap_net_admin=eip $(which rustnet)".to_string(),
"Set capabilities (modern Linux 5.8+, with eBPF): sudo setcap 'cap_net_raw,cap_bpf,cap_perfmon=eip' $(which rustnet)".to_string(),
"Set capabilities (legacy/older kernels, with eBPF): sudo setcap 'cap_net_raw,cap_sys_admin=eip' $(which rustnet)".to_string(),
"Set capabilities (packet capture only, no eBPF): sudo setcap 'cap_net_raw=eip' $(which rustnet)".to_string(),
];
// Add Docker-specific instructions if it looks like we're in a container
if is_running_in_container() {
instructions.push(
"If running in Docker, add these flags:\n \
--cap-add=NET_RAW --cap-add=NET_ADMIN --cap-add=BPF --cap-add=PERFMON --cap-add=SYS_PTRACE \
--cap-add=NET_RAW --cap-add=BPF --cap-add=PERFMON \
--net=host --pid=host"
.to_string(),
);
@@ -325,7 +306,10 @@ fn check_windows_privileges() -> Result<PrivilegeStatus> {
// Check if the error indicates a permissions issue
let error_str = e.to_string().to_lowercase();
if error_str.contains("access") || error_str.contains("denied") || error_str.contains("permission") {
if error_str.contains("access")
|| error_str.contains("denied")
|| error_str.contains("permission")
{
let missing = vec!["Administrator privileges".to_string()];
let instructions = vec![
@@ -336,7 +320,10 @@ fn check_windows_privileges() -> Result<PrivilegeStatus> {
Ok(PrivilegeStatus::insufficient(missing, instructions))
} else {
// Some other error - assume it's not a privilege issue
warn!("Network device enumeration failed but error doesn't indicate privilege issue: {}", e);
warn!(
"Network device enumeration failed but error doesn't indicate privilege issue: {}",
e
);
Ok(PrivilegeStatus::sufficient())
}
}