vibetunnel/tauri/src-tauri/src/network_utils.rs
2025-06-23 14:58:10 +02:00

297 lines
8.8 KiB
Rust

use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use tracing::error;
/// Network interface information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkInterface {
pub name: String,
pub addresses: Vec<IpAddress>,
pub is_up: bool,
pub is_loopback: bool,
}
/// IP address with type information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpAddress {
pub address: String,
pub is_ipv4: bool,
pub is_ipv6: bool,
pub is_private: bool,
}
/// Network utilities
pub struct NetworkUtils;
impl NetworkUtils {
/// Get the primary local IP address
pub fn get_local_ip_address() -> Option<String> {
// Try to get network interfaces
let interfaces = Self::get_all_interfaces();
// First, try to find a private network address (192.168.x.x, 10.x.x.x, 172.16-31.x.x)
for interface in &interfaces {
if interface.is_loopback || !interface.is_up {
continue;
}
for addr in &interface.addresses {
if addr.is_ipv4 && addr.is_private {
return Some(addr.address.clone());
}
}
}
// If no private address found, return any non-loopback IPv4
for interface in &interfaces {
if interface.is_loopback || !interface.is_up {
continue;
}
for addr in &interface.addresses {
if addr.is_ipv4 {
return Some(addr.address.clone());
}
}
}
None
}
/// Get all IP addresses
pub fn get_all_ip_addresses() -> Vec<String> {
let interfaces = Self::get_all_interfaces();
let mut addresses = Vec::new();
for interface in interfaces {
if interface.is_loopback {
continue;
}
for addr in interface.addresses {
addresses.push(addr.address);
}
}
addresses
}
/// Get all network interfaces
pub fn get_all_interfaces() -> Vec<NetworkInterface> {
#[cfg(unix)]
{
Self::get_interfaces_unix()
}
#[cfg(windows)]
{
Self::get_interfaces_windows()
}
#[cfg(not(any(unix, windows)))]
{
Vec::new()
}
}
#[cfg(unix)]
fn get_interfaces_unix() -> Vec<NetworkInterface> {
use nix::ifaddrs::getifaddrs;
let mut interfaces = std::collections::HashMap::new();
match getifaddrs() {
Ok(addrs) => {
for ifaddr in addrs {
let name = ifaddr.interface_name.clone();
let flags = ifaddr.flags;
let interface =
interfaces
.entry(name.clone())
.or_insert_with(|| NetworkInterface {
name,
addresses: Vec::new(),
is_up: flags.contains(nix::net::if_::InterfaceFlags::IFF_UP),
is_loopback: flags
.contains(nix::net::if_::InterfaceFlags::IFF_LOOPBACK),
});
if let Some(address) = ifaddr.address {
if let Some(sockaddr) = address.as_sockaddr_in() {
let ip = IpAddr::V4(Ipv4Addr::from(sockaddr.ip()));
interface.addresses.push(IpAddress {
address: ip.to_string(),
is_ipv4: true,
is_ipv6: false,
is_private: Self::is_private_ip(&ip),
});
} else if let Some(sockaddr) = address.as_sockaddr_in6() {
let ip = IpAddr::V6(sockaddr.ip());
interface.addresses.push(IpAddress {
address: ip.to_string(),
is_ipv4: false,
is_ipv6: true,
is_private: Self::is_private_ip(&ip),
});
}
}
}
}
Err(e) => {
error!("Failed to get network interfaces: {}", e);
}
}
interfaces.into_values().collect()
}
#[cfg(windows)]
fn get_interfaces_windows() -> Vec<NetworkInterface> {
use ipconfig::get_adapters;
let mut interfaces = Vec::new();
match get_adapters() {
Ok(adapters) => {
for adapter in adapters {
let mut addresses = Vec::new();
// Get IPv4 addresses
for addr in adapter.ipv4_addresses() {
addresses.push(IpAddress {
address: addr.to_string(),
is_ipv4: true,
is_ipv6: false,
is_private: Self::is_private_ipv4(addr),
});
}
// Get IPv6 addresses
for addr in adapter.ipv6_addresses() {
addresses.push(IpAddress {
address: addr.to_string(),
is_ipv4: false,
is_ipv6: true,
is_private: Self::is_private_ipv6(addr),
});
}
interfaces.push(NetworkInterface {
name: adapter.friendly_name().to_string(),
addresses,
is_up: adapter.oper_status() == ipconfig::OperStatus::IfOperStatusUp,
is_loopback: adapter.if_type() == ipconfig::IfType::SoftwareLoopback,
});
}
}
Err(e) => {
error!("Failed to get network interfaces: {}", e);
}
}
interfaces
}
/// Check if an IP address is private
const fn is_private_ip(ip: &IpAddr) -> bool {
match ip {
IpAddr::V4(ipv4) => Self::is_private_ipv4(ipv4),
IpAddr::V6(ipv6) => Self::is_private_ipv6(ipv6),
}
}
/// Check if an IPv4 address is private
const fn is_private_ipv4(ip: &Ipv4Addr) -> bool {
let octets = ip.octets();
// 10.0.0.0/8
if octets[0] == 10 {
return true;
}
// 172.16.0.0/12
if octets[0] == 172 && (octets[1] >= 16 && octets[1] <= 31) {
return true;
}
// 192.168.0.0/16
if octets[0] == 192 && octets[1] == 168 {
return true;
}
false
}
/// Check if an IPv6 address is private
const fn is_private_ipv6(ip: &Ipv6Addr) -> bool {
// Check for link-local addresses (fe80::/10)
let segments = ip.segments();
if segments[0] & 0xffc0 == 0xfe80 {
return true;
}
// Check for unique local addresses (fc00::/7)
if segments[0] & 0xfe00 == 0xfc00 {
return true;
}
false
}
/// Get hostname
pub fn get_hostname() -> Option<String> {
hostname::get()
.ok()
.and_then(|name| name.into_string().ok())
}
/// Test network connectivity to a host
pub async fn test_connectivity(host: &str, port: u16) -> bool {
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::time::timeout;
let addr = format!("{host}:{port}");
match timeout(Duration::from_secs(3), TcpStream::connect(&addr)).await {
Ok(Ok(_)) => true,
_ => false,
}
}
/// Get network statistics
pub fn get_network_stats() -> NetworkStats {
NetworkStats {
hostname: Self::get_hostname(),
primary_ip: Self::get_local_ip_address(),
all_ips: Self::get_all_ip_addresses(),
interface_count: Self::get_all_interfaces().len(),
}
}
}
/// Network statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkStats {
pub hostname: Option<String>,
pub primary_ip: Option<String>,
pub all_ips: Vec<String>,
pub interface_count: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_private_ipv4() {
assert!(NetworkUtils::is_private_ipv4(&"10.0.0.1".parse().unwrap()));
assert!(NetworkUtils::is_private_ipv4(
&"172.16.0.1".parse().unwrap()
));
assert!(NetworkUtils::is_private_ipv4(
&"192.168.1.1".parse().unwrap()
));
assert!(!NetworkUtils::is_private_ipv4(&"8.8.8.8".parse().unwrap()));
}
}