use std::{
collections::HashMap,
net::{IpAddr, Ipv6Addr},
};
use mousehop_ipc::IfaceKind;
use netdev::interface::types::InterfaceType;
pub(crate) fn current_network_id() -> Option<String> {
if let Ok(gw) = netdev::get_default_gateway() {
let mac = gw.mac_addr.to_string();
if !mac.is_empty() && mac != "00:00:00:00:00:00" {
return Some(format!("gw:{mac}"));
}
}
let iface = netdev::get_default_interface().ok()?;
let net = iface.ipv4.first()?;
Some(format!("net:{}/{}", net.network(), net.prefix_len()))
}
pub(crate) fn iface_kind(t: InterfaceType) -> IfaceKind {
match t {
InterfaceType::Ethernet
| InterfaceType::Ethernet3Megabit
| InterfaceType::FastEthernetT
| InterfaceType::FastEthernetFx
| InterfaceType::GigabitEthernet => IfaceKind::Wired,
InterfaceType::Wireless80211 | InterfaceType::PeerToPeerWireless => IfaceKind::WiFi,
_ => IfaceKind::Other,
}
}
const VIRTUAL_IFACE_PREFIXES: &[&str] = &[
"docker", "br-", "virbr", "veth", "vmnet", "vboxnet", "tap", "tun", "utun", "zt",
];
fn is_virtual_name(name: &str) -> bool {
VIRTUAL_IFACE_PREFIXES.iter().any(|p| name.starts_with(p))
}
pub(crate) fn is_routable_ip(ip: &IpAddr) -> bool {
if ip.is_loopback() || ip.is_unspecified() {
return false;
}
match ip {
IpAddr::V4(v4) => !v4.is_link_local(),
IpAddr::V6(v6) => !is_v6_link_local(v6),
}
}
fn is_v6_link_local(v6: &Ipv6Addr) -> bool {
(v6.segments()[0] & 0xffc0) == 0xfe80
}
pub(crate) fn default_interface_kind() -> Option<IfaceKind> {
netdev::get_default_interface()
.ok()
.map(|iface| iface_kind(iface.if_type))
}
pub(crate) fn local_addresses_with_kind() -> (Vec<IpAddr>, HashMap<IpAddr, IfaceKind>) {
let mut addrs = Vec::new();
let mut kinds = HashMap::new();
for iface in netdev::get_interfaces() {
let kind = iface_kind(iface.if_type);
if kind == IfaceKind::Other || is_virtual_name(&iface.name) {
continue;
}
let v4 = iface.ipv4.iter().map(|n| IpAddr::V4(n.addr()));
let v6 = iface.ipv6.iter().map(|n| IpAddr::V6(n.addr()));
for ip in v4.chain(v6) {
if is_routable_ip(&ip) {
addrs.push(ip);
kinds.insert(ip, kind);
}
}
}
(addrs, kinds)
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
#[test]
fn classifies_interface_types() {
assert_eq!(iface_kind(InterfaceType::GigabitEthernet), IfaceKind::Wired);
assert_eq!(iface_kind(InterfaceType::Wireless80211), IfaceKind::WiFi);
assert_eq!(iface_kind(InterfaceType::Tunnel), IfaceKind::Other);
assert_eq!(iface_kind(InterfaceType::Loopback), IfaceKind::Other);
assert_eq!(iface_kind(InterfaceType::Bridge), IfaceKind::Other);
}
#[test]
fn routable_filter_excludes_loopback_and_link_local() {
assert!(is_routable_ip(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 5))));
assert!(!is_routable_ip(&IpAddr::V4(Ipv4Addr::LOCALHOST)));
assert!(!is_routable_ip(&IpAddr::V4(Ipv4Addr::new(169, 254, 1, 1))));
assert!(!is_routable_ip(&"fe80::1".parse::<IpAddr>().unwrap()));
assert!(is_routable_ip(&"fdb7:7db7::1".parse::<IpAddr>().unwrap()));
}
#[test]
fn virtual_names_are_skipped() {
assert!(is_virtual_name("docker0"));
assert!(is_virtual_name("br-1a2b3c"));
assert!(is_virtual_name("veth9f8e"));
assert!(!is_virtual_name("enp191s0"));
assert!(!is_virtual_name("wlan0"));
}
}