use itertools::Itertools;
use pnet::{
datalink::NetworkInterface as PNetNetworkInterface, ipnetwork::IpNetwork,
util::MacAddr,
};
use std::{
net::{Ipv4Addr, TcpListener},
process::Command,
str::FromStr,
};
use crate::error::{RLanLibError, Result};
pub struct NetworkInterface {
pub name: String,
pub description: String,
pub cidr: String,
pub ipv4: Ipv4Addr,
pub ips: Vec<IpNetwork>,
pub mac: MacAddr,
pub flags: u32,
pub index: u32,
}
impl TryFrom<PNetNetworkInterface> for NetworkInterface {
type Error = RLanLibError;
fn try_from(value: PNetNetworkInterface) -> Result<Self> {
let mac = value.mac.ok_or(RLanLibError::NetworkInterface(
"failed to get mac address for interface".into(),
))?;
let (ip, cidr) = get_interface_ipv4_and_cidr(&value).ok_or(
RLanLibError::NetworkInterface(
"failed to get ip and cidr for interface".into(),
),
)?;
let ipv4 = Ipv4Addr::from_str(&ip).map_err(|e| {
RLanLibError::NetworkInterface(format!(
"failed to parse interface ip address '{ip}': {e}"
))
})?;
Ok(Self {
name: value.name,
description: value.description,
flags: value.flags,
index: value.index,
mac,
ips: value.ips,
cidr,
ipv4,
})
}
}
impl From<&NetworkInterface> for PNetNetworkInterface {
fn from(value: &NetworkInterface) -> Self {
Self {
name: value.name.clone(),
flags: value.flags,
description: value.description.clone(),
index: value.index,
ips: value.ips.clone(),
mac: Some(value.mac),
}
}
}
pub fn get_interface(name: &str) -> Result<NetworkInterface> {
let iface = pnet::datalink::interfaces()
.into_iter()
.find(|i| i.name == name)
.ok_or(RLanLibError::NetworkInterface(format!(
"failed to find network interface with name: {name}"
)))?;
NetworkInterface::try_from(iface)
}
pub fn get_default_interface() -> Result<NetworkInterface> {
let iface = pnet::datalink::interfaces()
.into_iter()
.find(|e| {
e.is_up() && !e.is_loopback() && e.ips.iter().any(|i| i.is_ipv4())
})
.ok_or(RLanLibError::NetworkInterface(
"failed to get default network interface".into(),
))?;
NetworkInterface::try_from(iface)
}
pub fn get_available_port() -> Result<u16> {
let listener = TcpListener::bind(("127.0.0.1", 0)).map_err(|e| {
RLanLibError::NetworkInterface(format!(
"failed to bind loopback to find open port: {e}"
))
})?;
let addr = listener.local_addr().map_err(|e| {
RLanLibError::NetworkInterface(format!(
"failed to get local address for open port: {e}"
))
})?;
Ok(addr.port())
}
fn get_interface_ipv4_and_cidr(
interface: &PNetNetworkInterface,
) -> Option<(String, String)> {
let ipnet = interface.ips.iter().find(|i| i.is_ipv4())?;
let host_ip = ipnet.ip().to_string();
let first_ip = ipnet
.iter()
.find_or_first(|p| p.is_ipv4() && !p.to_string().ends_with(".0"));
let base = first_ip
.map(|i| i.to_string())
.unwrap_or_else(|| ipnet.network().to_string());
let prefix = ipnet.prefix().to_string();
let cidr = format!("{base}/{prefix}");
Some((host_ip, cidr))
}
#[cfg(target_os = "macos")]
pub fn get_default_gateway() -> Option<Ipv4Addr> {
let output = Command::new("netstat").args(["-rn"]).output().ok()?;
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
let mut parts = line.split_whitespace();
if parts.next() == Some("default")
&& let Some(gw) = parts.next()
&& let Ok(ip) = Ipv4Addr::from_str(gw)
{
return Some(ip);
}
}
None
}
#[cfg(target_os = "linux")]
pub fn get_default_gateway() -> Option<Ipv4Addr> {
let output = Command::new("ip").args(["route", "show"]).output().ok()?;
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
let mut parts = line.split_whitespace();
if parts.next() == Some("default")
&& parts.next() == Some("via")
&& let Some(gw) = parts.next()
&& let Ok(ip) = Ipv4Addr::from_str(gw)
{
return Some(ip);
}
}
None
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
pub fn get_default_gateway() -> Option<Ipv4Addr> {
None
}
#[cfg(test)]
#[path = "./network_tests.rs"]
mod tests;