mirror of
https://github.com/domcyrus/rustnet.git
synced 2025-12-20 13:40:17 -06:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
89
INSTALL.md
89
INSTALL.md
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
14
README.md
14
README.md
@@ -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:**
|
||||
|
||||
4
USAGE.md
4
USAGE.md
@@ -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
28
debian/postinst
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user