mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-11 12:15:53 +00:00
297 lines
8.8 KiB
Rust
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()));
|
|
}
|
|
}
|