Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
scripts/* linguist-vendored
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@ required-features = ["serde", "gateway"]
name = "global_ips"
path = "examples/global_ips.rs"
required-features = ["gateway"]

[[example]]
name = "stats"
path = "examples/stats.rs"
1 change: 1 addition & 0 deletions examples/default_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fn main() {
println!("\tIPv6: {:?}", interface.ipv6);
println!("\tTransmit Speed: {:?}", interface.transmit_speed);
println!("\tReceive Speed: {:?}", interface.receive_speed);
println!("\tStats: {:?}", interface.stats);
if let Some(gateway) = interface.gateway {
println!("Default Gateway");
println!("\tMAC Address: {}", gateway.mac_addr);
Expand Down
1 change: 1 addition & 0 deletions examples/list_interfaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ fn main() {

println!("\tTransmit Speed: {:?}", interface.transmit_speed);
println!("\tReceive Speed: {:?}", interface.receive_speed);
println!("\tStats: {:?}", interface.stats);
#[cfg(feature = "gateway")]
if let Some(gateway) = interface.gateway {
println!("Gateway");
Expand Down
42 changes: 42 additions & 0 deletions examples/stats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use std::thread::sleep;
use std::time::{Duration, SystemTime};

use netdev::{self, Interface};

fn main() -> std::io::Result<()> {
let mut iface = netdev::get_default_interface().expect("No default interface found");
println!(
"Monitoring default interface: [{}]{}\n",
iface.index, iface.name
);

// Initial stats
println!("[Initial stats]");
print_stats(&iface);

// Update stats every second for 3 seconds
for i in 1..=3 {
sleep(Duration::from_secs(1));
iface.update_stats()?;
println!("\n[Update {}]", i);
print_stats(&iface);
}

Ok(())
}

fn print_stats(iface: &Interface) {
match &iface.stats {
Some(stats) => {
println!(
"RX: {:>12} bytes, TX: {:>12} bytes at {:?}",
stats.rx_bytes,
stats.tx_bytes,
stats.timestamp.unwrap_or(SystemTime::UNIX_EPOCH)
);
}
None => {
println!("No statistics available for interface: {}", iface.name);
}
}
}
20 changes: 20 additions & 0 deletions scripts/build-all.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# target platforms
$targets = @(
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"x86_64-unknown-freebsd",
"aarch64-linux-android",
"x86_64-linux-android"
)

# cross build
foreach ($target in $targets) {
Write-Host "==> Building for $target..."
$result = & cross build --target $target
Write-Host "✅ Success: $target"
if ($LASTEXITCODE -ne 0) {
Write-Error "❌ Build failed for $target"
exit 1
}
}
Write-Host "✅ All builds succeeded."
25 changes: 25 additions & 0 deletions scripts/build-all.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -e

# target platforms
TARGETS=(
x86_64-unknown-linux-gnu
aarch64-unknown-linux-gnu
x86_64-unknown-freebsd
aarch64-linux-android
x86_64-linux-android
)

# cross build
for target in "${TARGETS[@]}"; do
echo "==> Building for $target..."
if cross build --target "$target"; then
echo "✅ Success: $target"
else
echo "❌ Failed: $target"
exit 1
fi
done

echo ""
echo "✅ All builds succeeded!"
7 changes: 7 additions & 0 deletions src/interface/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub mod netlink {
mac::MacAddr,
};

use crate::stats::{get_stats, InterfaceStats};

pub fn unix_interfaces() -> Vec<Interface> {
let mut ifaces = Vec::new();
if let Ok(socket) = Socket::new(NETLINK_ROUTE) {
Expand All @@ -86,6 +88,10 @@ pub mod netlink {
eprintln!("unable to list addresses: {:?}", err);
}
}
for iface in &mut ifaces {
let stats: Option<InterfaceStats> = get_stats(None, &iface.name);
iface.stats = stats;
}
ifaces
}

Expand All @@ -105,6 +111,7 @@ pub mod netlink {
flags: link_msg.header.flags.bits(),
transmit_speed: None,
receive_speed: None,
stats: None,
#[cfg(feature = "gateway")]
gateway: None,
#[cfg(feature = "gateway")]
Expand Down
16 changes: 16 additions & 0 deletions src/interface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ use crate::device::NetworkDevice;
use crate::ip::{is_global_ip, is_global_ipv4, is_global_ipv6};
use crate::ipnet::{Ipv4Net, Ipv6Net};
use crate::mac::MacAddr;
use crate::stats::InterfaceStats;
use crate::sys;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

Expand Down Expand Up @@ -90,6 +91,16 @@ pub struct Interface {
/// Speed in bits per second of the receive for the network interface.
/// Currently only supported on Linux, Android, and Windows.
pub receive_speed: Option<u64>,
/// Statistics for this network interface, such as received and transmitted bytes.
///
/// This field is populated at the time of interface discovery
/// (e.g., via [`get_interfaces()`] or [`get_default_interface()`]).
///
/// The values represent a snapshot of total RX and TX bytes since system boot,
/// and include a timestamp (`SystemTime`) indicating when the snapshot was taken.
///
/// If more up-to-date statistics are needed, use [`Interface::update_stats()`] to refresh this field.
pub stats: Option<InterfaceStats>,
/// Default gateway for the network interface. This is the address of the router to which
/// IP packets are forwarded when they need to be sent to a device outside
/// of the local network.
Expand Down Expand Up @@ -150,6 +161,7 @@ impl Interface {
flags: 0,
transmit_speed: None,
receive_speed: None,
stats: None,
#[cfg(feature = "gateway")]
gateway: None,
#[cfg(feature = "gateway")]
Expand Down Expand Up @@ -250,6 +262,10 @@ impl Interface {
.filter(|ip| is_global_ip(ip))
.collect()
}
/// Updates the runtime traffic statistics for this interface (e.g., rx/tx byte counters).
pub fn update_stats(&mut self) -> std::io::Result<()> {
crate::stats::update_interface_stats(self)
}
}

/// Get default Network Interface
Expand Down
7 changes: 7 additions & 0 deletions src/interface/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use super::MacAddr;
use crate::gateway;
use crate::interface::InterfaceType;
use crate::ipnet::{Ipv4Net, Ipv6Net};
use crate::stats::{get_stats, InterfaceStats};
use crate::sys;
use libc;
use std::ffi::{CStr, CString};
Expand Down Expand Up @@ -407,6 +408,7 @@ fn unix_interfaces_inner(
let (mac, ip, ipv6_scope_id) =
sockaddr_to_network_addr(addr_ref.ifa_addr as *mut libc::sockaddr);
let (_, netmask, _) = sockaddr_to_network_addr(addr_ref.ifa_netmask as *mut libc::sockaddr);
let stats: Option<InterfaceStats> = get_stats(Some(addr_ref), &name);
let mut ini_ipv4: Option<Ipv4Net> = None;
let mut ini_ipv6: Option<Ipv6Net> = None;
if let Some(ip) = ip {
Expand Down Expand Up @@ -455,6 +457,10 @@ fn unix_interfaces_inner(
iface.mac_addr = Some(mac);
}

if iface.stats.is_none() {
iface.stats = stats.clone();
}

if ini_ipv4.is_some() {
iface.ipv4.push(ini_ipv4.unwrap());
}
Expand Down Expand Up @@ -489,6 +495,7 @@ fn unix_interfaces_inner(
flags: addr_ref.ifa_flags,
transmit_speed: None,
receive_speed: None,
stats: stats,
#[cfg(feature = "gateway")]
gateway: None,
#[cfg(feature = "gateway")]
Expand Down
3 changes: 3 additions & 0 deletions src/interface/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::device::NetworkDevice;
use crate::interface::{Interface, InterfaceType};
use crate::ipnet::{Ipv4Net, Ipv6Net};
use crate::mac::MacAddr;
use crate::stats::InterfaceStats;
use crate::sys;
use std::ffi::CStr;
use std::mem::MaybeUninit;
Expand Down Expand Up @@ -265,6 +266,7 @@ pub fn interfaces() -> Vec<Interface> {
IpAddr::V4(local_ipv4) => ipv4_vec.iter().any(|x| x.addr() == local_ipv4),
IpAddr::V6(local_ipv6) => ipv6_vec.iter().any(|x| x.addr() == local_ipv6),
};
let stats: Option<InterfaceStats> = crate::stats::get_stats_from_index(index);
let interface: Interface = Interface {
index,
name: adapter_name,
Expand All @@ -278,6 +280,7 @@ pub fn interfaces() -> Vec<Interface> {
flags,
transmit_speed: sys::sanitize_u64(cur.TransmitLinkSpeed),
receive_speed: sys::sanitize_u64(cur.ReceiveLinkSpeed),
stats,
#[cfg(feature = "gateway")]
gateway: if default_gateway.mac_addr == MacAddr::zero() {
None
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod gateway;
pub mod interface;
mod ip;
pub mod mac;
pub mod stats;
mod sys;

pub use device::NetworkDevice;
Expand Down
Loading
Loading