use super::os::{is_physical, is_wireless};
use crate::info;
use pnet::datalink::NetworkInterface;
use pnet::ipnetwork::{IpNetwork, Ipv4Network};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ViabilityError {
IsDown,
NotPhysical,
NoMacAddress,
NotBroadcast,
IsPointToPoint,
NoValidLanIp,
}
pub fn get_lan_network() -> anyhow::Result<Option<Ipv4Network>> {
let interfaces: Vec<NetworkInterface> = pnet::datalink::interfaces();
get_lan_network_with(interfaces)
}
pub(crate) fn get_lan_network_with(
interfaces: Vec<NetworkInterface>,
) -> anyhow::Result<Option<Ipv4Network>> {
let interfaces_str: &str = match interfaces.len() {
1 => "interface",
_ => "interfaces",
};
info!(
verbosity = 1,
"Identified {} network {}, picking the best one...",
interfaces.len(),
interfaces_str
);
let interfaces: Vec<NetworkInterface> = interfaces
.into_iter()
.filter_map(
|interface| match is_viable_lan_interface(&interface, is_physical) {
Ok(()) => Some(interface),
Err(_) => None,
},
)
.collect();
let interface: NetworkInterface =
if let Some(interface) = select_best_lan_interface(interfaces, is_wired) {
info!(
verbosity = 1,
"Performing LAN scan on interface {}", interface.name
);
interface
} else {
anyhow::bail!("No interfaces available for LAN discovery");
};
let private_v4_net: Option<Ipv4Network> = interface.ips.iter().find_map(|net| match net {
IpNetwork::V4(v4) if v4.ip().is_private() => Some(*v4),
_ => None,
});
Ok(private_v4_net)
}
fn is_viable_lan_interface(
interface: &NetworkInterface,
is_physical: impl Fn(&NetworkInterface) -> bool,
) -> Result<(), ViabilityError> {
if !interface.is_up() {
return Err(ViabilityError::IsDown);
}
if !is_physical(interface) {
return Err(ViabilityError::NotPhysical);
}
if interface.is_loopback() {
return Err(ViabilityError::NotPhysical);
}
if interface.mac.is_none() {
return Err(ViabilityError::NoMacAddress);
}
if !interface.is_broadcast() {
return Err(ViabilityError::NotBroadcast);
}
if interface.is_point_to_point() {
return Err(ViabilityError::IsPointToPoint);
}
let has_valid_ip = interface.ips.iter().any(|net| match net {
IpNetwork::V4(ipv4) => ipv4.ip().is_private(),
IpNetwork::V6(ipv6) => ipv6.ip().is_unicast_link_local(),
});
if !has_valid_ip {
return Err(ViabilityError::NoValidLanIp);
}
Ok(())
}
fn select_best_lan_interface(
interfaces: Vec<NetworkInterface>,
is_wired: impl Fn(&NetworkInterface) -> bool,
) -> Option<NetworkInterface> {
match interfaces.len() {
0 => None,
1 => Some(interfaces[0].clone()),
_ => interfaces
.iter()
.find(|&interface| is_wired(interface))
.cloned()
.or(Some(interfaces[0].clone())),
}
}
pub fn is_wired(interface: &NetworkInterface) -> bool {
is_physical(interface) && !is_wireless(interface)
}
#[cfg(test)]
mod tests {
use super::*;
use pnet::datalink::MacAddr;
use pnet::ipnetwork::IpNetwork;
use std::net::Ipv4Addr;
fn mock_interface(
up: bool,
mac: bool,
broadcast: bool,
p2p: bool,
loopback: bool,
ip: bool,
) -> NetworkInterface {
NetworkInterface {
name: "test0".to_string(),
description: "".to_string(),
index: 0,
mac: if mac {
Some(MacAddr::new(1, 2, 3, 4, 5, 6))
} else {
None
},
ips: if ip {
vec![IpNetwork::V4(
Ipv4Network::new(Ipv4Addr::new(192, 168, 1, 100), 24).unwrap(),
)]
} else {
vec![]
},
flags: {
let mut flags = 0;
if up {
flags |= 1;
}
if broadcast {
flags |= 2;
}
if p2p {
flags |= 16;
}
if loopback {
flags |= 8;
} flags
},
}
}
#[test]
fn is_viable_down() {
let intf = mock_interface(false, true, true, false, false, true);
assert_eq!(
is_viable_lan_interface(&intf, |_| true),
Err(ViabilityError::IsDown)
);
}
#[test]
fn is_viable_not_physical() {
let intf = mock_interface(true, true, true, false, false, true);
assert_eq!(
is_viable_lan_interface(&intf, |_| false),
Err(ViabilityError::NotPhysical)
);
}
#[test]
fn is_viable_no_mac() {
let intf = mock_interface(true, false, true, false, false, true);
assert_eq!(
is_viable_lan_interface(&intf, |_| true),
Err(ViabilityError::NoMacAddress)
);
}
#[test]
fn is_viable_success() {
let intf = mock_interface(true, true, true, false, false, true);
assert_eq!(is_viable_lan_interface(&intf, |_| true), Ok(()));
}
}