Skip to content

Commit 6e14261

Browse files
authored
Feature/interface stats (#79)
* feat: adding interface stats * macOS specific improvements * fix windows interface stats
1 parent eb4b72d commit 6e14261

13 files changed

Lines changed: 1289 additions & 14 deletions

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,14 @@ libbpf-rs = { version = "0.25", optional = true }
4949
bytes = { version = "1.11", optional = true }
5050
libc = { version = "0.2", optional = true }
5151

52+
[target.'cfg(any(target_os = "macos", target_os = "freebsd"))'.dependencies]
53+
libc = "0.2"
54+
5255
[target.'cfg(windows)'.dependencies]
5356
windows = { version = "0.62", features = [
5457
"Win32_Foundation",
5558
"Win32_NetworkManagement_IpHelper",
59+
"Win32_NetworkManagement_Ndis",
5660
"Win32_Networking_WinSock",
5761
"Win32_System_LibraryLoader",
5862
"Win32_System_Threading",
@@ -74,6 +78,7 @@ zip = "6.0"
7478
windows = { version = "0.62", features = [
7579
"Win32_Foundation",
7680
"Win32_NetworkManagement_IpHelper",
81+
"Win32_NetworkManagement_Ndis",
7782
"Win32_Networking_WinSock",
7883
"Win32_System_LibraryLoader",
7984
"Win32_System_Threading",

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ A cross-platform network monitoring tool built with Rust. RustNet provides real-
1515

1616
- **Real-time Network Monitoring**: Monitor active TCP, UDP, ICMP, and ARP connections with detailed state information
1717
- **Connection States**: Track TCP states (`ESTABLISHED`, `SYN_SENT`, `TIME_WAIT`), QUIC states (`QUIC_INITIAL`, `QUIC_HANDSHAKE`, `QUIC_CONNECTED`), DNS states, SSH states, and activity-based UDP states
18+
- **Interface Statistics**: Real-time monitoring of network interface metrics including bytes/packets transferred, errors, drops, and collisions
1819
- **Deep Packet Inspection (DPI)**: Detect application protocols including HTTP, HTTPS/TLS with SNI, DNS, SSH with version detection, and QUIC with CONNECTION_CLOSE frame detection
1920
- **TCP Network Analytics**: Real-time detection of TCP retransmissions, out-of-order packets, and fast retransmits with per-connection and aggregate statistics
2021
- **Smart Connection Lifecycle**: Protocol-aware timeouts with visual staleness indicators (white → yellow → red) before cleanup
@@ -56,6 +57,28 @@ See [EBPF_BUILD.md](EBPF_BUILD.md) for more details and [ARCHITECTURE.md](ARCHIT
5657

5758
</details>
5859

60+
<details>
61+
<summary><b>Interface Statistics Monitoring</b></summary>
62+
63+
RustNet provides real-time network interface statistics across all supported platforms:
64+
65+
- **Overview Tab**: Shows active interfaces with current rates, errors, and drops
66+
- **Interfaces Tab** (press `i`): Detailed table with comprehensive metrics for all interfaces
67+
- **Cross-Platform**: Linux (sysfs), macOS/FreeBSD (getifaddrs), Windows (GetIfTable2 API)
68+
- **Smart Filtering**: Windows automatically excludes virtual/filter adapters
69+
70+
See [USAGE.md](USAGE.md#interface-statistics) for detailed documentation on interpreting interface statistics and platform-specific behavior.
71+
72+
**Metrics Available:**
73+
- Total bytes and packets (RX/TX)
74+
- Error counters (receive and transmit)
75+
- Packet drops (queue overflows)
76+
- Collisions (legacy, rarely used on modern networks)
77+
78+
Stats are collected every 2 seconds in a background thread with minimal performance impact.
79+
80+
</details>
81+
5982
## Quick Start
6083

6184
### Installation
@@ -121,6 +144,7 @@ See [INSTALL.md](INSTALL.md) for detailed permission setup and [USAGE.md](USAGE.
121144
| `q` | Quit (press twice to confirm) |
122145
| `Ctrl+C` | Quit immediately |
123146
| `Tab` | Switch between tabs |
147+
| `i` | Toggle interface statistics view |
124148
| `↑/k` `↓/j` | Navigate up/down |
125149
| `g` `G` | Jump to first/last connection |
126150
| `Enter` | View connection details |

USAGE.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This guide covers detailed usage of RustNet, including command-line options, key
1010
- [Filtering](#filtering)
1111
- [Sorting](#sorting)
1212
- [Network Statistics Panel](#network-statistics-panel)
13+
- [Interface Statistics](#interface-statistics)
1314
- [Connection Lifecycle & Visual Indicators](#connection-lifecycle--visual-indicators)
1415
- [Logging](#logging)
1516

@@ -192,6 +193,7 @@ Log files are created in the `logs/` directory with timestamp: `rustnet_YYYY-MM-
192193
### Views and Tabs
193194

194195
- `Tab` - Switch between tabs (Overview, Details, Help)
196+
- `i` - Toggle Interface Statistics view
195197
- `Enter` - View detailed information about selected connection
196198
- `Esc` - Go back to previous view or clear active filter
197199
- `h` - Toggle help screen
@@ -463,6 +465,126 @@ Fast retransmit frequency indicates how well TCP is recovering from packet loss
463465
- SYN and FIN flags are properly accounted for in sequence number tracking (each consumes 1 sequence number)
464466
- Only TCP connections show analytics; UDP, ICMP, and other protocols do not have these metrics
465467

468+
## Interface Statistics
469+
470+
RustNet provides real-time network interface statistics across all supported platforms (Linux, macOS, FreeBSD, Windows). Interface stats are displayed in two locations:
471+
472+
### Accessing Interface Statistics
473+
474+
**Overview Tab (Main Screen):**
475+
- Interface stats appear in the right panel below Network Stats
476+
- Shows up to 3 active interfaces with current rates
477+
- Displays: `InterfaceName: X KB/s ↓ / Y KB/s ↑`
478+
- Shows cumulative totals: `Errors (Total): N Drops (Total): M`
479+
480+
**Interfaces Tab (Detailed View):**
481+
- Press `i` to toggle the Interface Statistics view
482+
- Shows a detailed table of all network interfaces
483+
- Displays comprehensive metrics for each interface
484+
485+
### Statistics Displayed
486+
487+
| Metric | Description | Notes |
488+
|--------|-------------|-------|
489+
| **RX Rate** | Current receive rate (bytes/sec) | Calculated from recent activity |
490+
| **TX Rate** | Current transmit rate (bytes/sec) | Calculated from recent activity |
491+
| **RX Packets** | Total packets received | Cumulative since boot/interface up |
492+
| **TX Packets** | Total packets transmitted | Cumulative since boot/interface up |
493+
| **RX Err** | Receive errors | Cumulative total (not recent) |
494+
| **TX Err** | Transmit errors | Cumulative total (not recent) |
495+
| **RX Drop** | Dropped incoming packets | Cumulative total (not recent) |
496+
| **TX Drop** | Dropped outgoing packets | Cumulative total (not recent) |
497+
| **Collisions** | Network collisions | Platform-dependent availability |
498+
499+
**Important**: Error and drop counters are **cumulative totals** since the system booted or the interface came up, not recent activity. These help identify long-term interface reliability but won't show immediate issues.
500+
501+
### Platform-Specific Behavior
502+
503+
**All Platforms:**
504+
- All counters (bytes, packets, errors, drops) are cumulative from boot/interface up
505+
- Rates (bytes/sec) are calculated from snapshots taken every 2 seconds
506+
- Loopback interface is included for monitoring local traffic
507+
508+
**Windows:**
509+
- Filters out virtual/filter adapters to show only physical interfaces:
510+
- Excludes: `-Npcap`, `-WFP`, `-QoS`, `-Native`, `-Virtual`, `-Packet` variants
511+
- Excludes: `Lightweight Filter`, `MAC Layer` interfaces
512+
- Excludes: Disconnected "Local Area Connection" adapters
513+
- Uses LUID-based deduplication to prevent duplicate interface entries
514+
- Collisions: Always 0 (not available on modern Windows interfaces)
515+
516+
**macOS:**
517+
- Includes data validation to detect corrupt counters on virtual interfaces
518+
- TX Drops: Always 0 (limited availability on macOS)
519+
- Sanitizes error/drop counters if values appear corrupted (>2^31 or errors>packets)
520+
521+
**FreeBSD:**
522+
- TX Drops: Always 0 (not typically available on FreeBSD)
523+
- Uses BSD getifaddrs API with AF_LINK filtering
524+
525+
**Linux:**
526+
- Reads statistics from `/sys/class/net/{interface}/statistics`
527+
- All counters typically available and reliable
528+
529+
### Interpreting the Statistics
530+
531+
**Healthy Interface:**
532+
```
533+
Ethernet: 2.40 KB/s ↓ / 1.96 KB/s ↑
534+
Errors (Total): 0 Drops (Total): 0
535+
```
536+
Zero or very low error/drop counts indicate a reliable network connection.
537+
538+
**Problematic Interface:**
539+
```
540+
WiFi: 150 KB/s ↓ / 45 KB/s ↑
541+
Errors (Total): 1089 Drops (Total): 2178
542+
```
543+
High error/drop counts may indicate:
544+
- Signal interference (WiFi)
545+
- Cable issues (Ethernet)
546+
- Network congestion
547+
- Driver or hardware problems
548+
549+
**Note**: Since error/drop counters are cumulative, evaluate them relative to total packets. A few errors out of millions of packets is normal; thousands of errors with low packet counts indicates problems.
550+
551+
### Interface Filtering
552+
553+
**Which Interfaces Are Shown:**
554+
- Interfaces must be operationally "up" OR have traffic statistics
555+
- Loopback interface is included (useful for monitoring local connections)
556+
- Virtual/filter adapters are excluded on Windows (they mirror physical interfaces)
557+
558+
**Overview Tab Filtering:**
559+
- Windows: Shows all active interfaces (NPF device path detected automatically)
560+
- macOS/Linux: Shows interfaces with recent traffic (`rx_bytes > 0 || tx_bytes > 0 || rx_packets > 0 || tx_packets > 0`)
561+
- Special interfaces (`any`, `pktap`): Shows all interfaces with any activity
562+
563+
**Interfaces Tab:**
564+
- Shows all detected interfaces that pass the platform-specific filters
565+
- Sorts to show the currently captured interface first (highlighted)
566+
- Other interfaces appear in alphabetical order
567+
568+
### Use Cases
569+
570+
**Bandwidth Monitoring:**
571+
Monitor real-time bandwidth usage across all network interfaces to identify:
572+
- Which interface is carrying the most traffic
573+
- Bandwidth distribution across WiFi vs Ethernet
574+
- Local traffic volume (loopback interface)
575+
576+
**Reliability Analysis:**
577+
Check cumulative error and drop counters to:
578+
- Identify unreliable network interfaces
579+
- Detect hardware or driver issues
580+
- Compare interface quality over time
581+
582+
**Multi-Interface Systems:**
583+
On systems with multiple network interfaces:
584+
- Compare performance across interfaces
585+
- Monitor VPN tunnel statistics
586+
- Track interface failover behavior
587+
466588
## Connection Lifecycle & Visual Indicators
467589

468590
RustNet uses intelligent timeout management to automatically clean up inactive connections while providing visual warnings before removal.

src/app.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,24 @@ use crate::filter::ConnectionFilter;
1515

1616
use crate::network::{
1717
capture::{CaptureConfig, PacketReader, setup_packet_capture},
18+
interface_stats::{InterfaceStats, InterfaceStatsProvider, InterfaceRates},
1819
merge::{create_connection_from_packet, merge_packet_into_connection},
1920
parser::{PacketParser, ParsedPacket, ParserConfig},
2021
platform::create_process_lookup,
2122
services::ServiceLookup,
2223
types::{ApplicationProtocol, Connection, Protocol},
2324
};
2425

26+
// Platform-specific interface stats provider
27+
#[cfg(target_os = "linux")]
28+
use crate::network::platform::LinuxStatsProvider as PlatformStatsProvider;
29+
#[cfg(target_os = "freebsd")]
30+
use crate::network::platform::FreeBSDStatsProvider as PlatformStatsProvider;
31+
#[cfg(target_os = "macos")]
32+
use crate::network::platform::MacOSStatsProvider as PlatformStatsProvider;
33+
#[cfg(target_os = "windows")]
34+
use crate::network::platform::WindowsStatsProvider as PlatformStatsProvider;
35+
2536
use std::collections::HashMap;
2637
use std::sync::{LazyLock, Mutex};
2738

@@ -190,6 +201,12 @@ pub struct App {
190201

191202
/// Current process detection method (e.g., "eBPF + procfs", "pktap", "lsof", "N/A")
192203
process_detection_method: Arc<RwLock<String>>,
204+
205+
/// Interface statistics (cumulative totals)
206+
interface_stats: Arc<DashMap<String, InterfaceStats>>,
207+
208+
/// Interface rates (per-second rates)
209+
interface_rates: Arc<DashMap<String, InterfaceRates>>,
193210
}
194211

195212
impl App {
@@ -212,6 +229,8 @@ impl App {
212229
linktype: Arc::new(RwLock::new(None)),
213230
pktap_active: Arc::new(AtomicBool::new(false)),
214231
process_detection_method: Arc::new(RwLock::new(String::from("initializing..."))),
232+
interface_stats: Arc::new(DashMap::new()),
233+
interface_rates: Arc::new(DashMap::new()),
215234
})
216235
}
217236

@@ -237,6 +256,9 @@ impl App {
237256
// Start rate refresh thread
238257
self.start_rate_refresh_thread(connections)?;
239258

259+
// Start interface stats collection thread
260+
self.start_interface_stats_thread()?;
261+
240262
// Mark loading as complete after a short delay
241263
let is_loading = Arc::clone(&self.is_loading);
242264
thread::spawn(move || {
@@ -769,6 +791,56 @@ impl App {
769791
Ok(())
770792
}
771793

794+
/// Start interface statistics collection thread
795+
fn start_interface_stats_thread(&self) -> Result<()> {
796+
let should_stop = Arc::clone(&self.should_stop);
797+
let interface_stats = Arc::clone(&self.interface_stats);
798+
let interface_rates = Arc::clone(&self.interface_rates);
799+
800+
thread::spawn(move || {
801+
info!("Interface stats collection thread started");
802+
803+
let provider = PlatformStatsProvider;
804+
let mut previous_stats: HashMap<String, InterfaceStats> = HashMap::new();
805+
806+
loop {
807+
if should_stop.load(Ordering::Relaxed) {
808+
info!("Interface stats thread stopping");
809+
break;
810+
}
811+
812+
// Collect stats from all interfaces
813+
match provider.get_all_stats() {
814+
Ok(stats_vec) => {
815+
// Clear old entries
816+
interface_stats.clear();
817+
interface_rates.clear();
818+
819+
for stat in stats_vec {
820+
// Calculate rates if we have previous data
821+
if let Some(prev) = previous_stats.get(&stat.interface_name) {
822+
let rates = stat.calculate_rates(prev);
823+
interface_rates.insert(stat.interface_name.clone(), rates);
824+
}
825+
826+
// Store current stats
827+
interface_stats.insert(stat.interface_name.clone(), stat.clone());
828+
previous_stats.insert(stat.interface_name.clone(), stat);
829+
}
830+
}
831+
Err(e) => {
832+
debug!("Failed to collect interface stats: {}", e);
833+
}
834+
}
835+
836+
// Refresh every 2 seconds
837+
thread::sleep(Duration::from_secs(2));
838+
}
839+
});
840+
841+
Ok(())
842+
}
843+
772844
/// Start cleanup thread to remove old connections
773845
fn start_cleanup_thread(&self, connections: Arc<DashMap<String, Connection>>) -> Result<()> {
774846
let should_stop = Arc::clone(&self.should_stop);
@@ -870,6 +942,22 @@ impl App {
870942
.collect()
871943
}
872944

945+
/// Get interface statistics
946+
pub fn get_interface_stats(&self) -> Vec<InterfaceStats> {
947+
self.interface_stats
948+
.iter()
949+
.map(|entry| entry.value().clone())
950+
.collect()
951+
}
952+
953+
/// Get interface rates (bytes/sec)
954+
pub fn get_interface_rates(&self) -> HashMap<String, InterfaceRates> {
955+
self.interface_rates
956+
.iter()
957+
.map(|entry| (entry.key().clone(), entry.value().clone()))
958+
.collect()
959+
}
960+
873961
/// Get application statistics
874962
pub fn get_stats(&self) -> AppStats {
875963
AppStats {

src/main.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,20 +364,30 @@ fn run_ui_loop<B: ratatui::prelude::Backend>(
364364
// Tab navigation
365365
(KeyCode::Tab, _) => {
366366
ui_state.quit_confirmation = false;
367-
ui_state.selected_tab = (ui_state.selected_tab + 1) % 3;
367+
ui_state.selected_tab = (ui_state.selected_tab + 1) % 4;
368368
}
369369

370370
// Help toggle
371371
(KeyCode::Char('h'), _) => {
372372
ui_state.quit_confirmation = false;
373373
ui_state.show_help = !ui_state.show_help;
374374
if ui_state.show_help {
375-
ui_state.selected_tab = 2; // Switch to help tab
375+
ui_state.selected_tab = 3; // Switch to help tab
376376
} else {
377377
ui_state.selected_tab = 0; // Back to overview
378378
}
379379
}
380380

381+
// Interface stats toggle (shortcut to Interface tab)
382+
(KeyCode::Char('i'), _) | (KeyCode::Char('I'), _) => {
383+
ui_state.quit_confirmation = false;
384+
if ui_state.selected_tab == 2 {
385+
ui_state.selected_tab = 0; // Back to overview
386+
} else {
387+
ui_state.selected_tab = 2; // Switch to interfaces tab
388+
}
389+
}
390+
381391
// Navigation in connection list
382392
(KeyCode::Up, _) | (KeyCode::Char('k'), _) => {
383393
ui_state.quit_confirmation = false;

0 commit comments

Comments
 (0)