use std::{collections::HashMap, fmt, net::IpAddr};
use n0_future::time::Instant;
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "macos",
target_os = "ios"
))]
pub(super) mod bsd;
#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux;
#[cfg(target_os = "windows")]
mod windows;
pub use netdev::interface::ipv6_addr_flags::Ipv6AddrFlags;
pub(crate) use netdev::ipnet::{Ipv4Net, Ipv6Net};
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "macos",
target_os = "ios"
))]
use self::bsd::default_route;
#[cfg(any(target_os = "linux", target_os = "android"))]
use self::linux::default_route;
#[cfg(target_os = "windows")]
use self::windows::default_route;
#[cfg(not(wasm_browser))]
use crate::ip::is_link_local;
use crate::ip::{LocalAddresses, is_private_v6, is_up};
#[cfg(not(wasm_browser))]
use crate::netmon::is_interesting_interface;
#[derive(Debug, Clone)]
pub struct Interface {
iface: netdev::Interface,
}
impl fmt::Display for Interface {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}. {} {:?} ipv4={:?} ipv6={:?}",
self.iface.index, self.iface.name, self.iface.if_type, self.iface.ipv4, self.iface.ipv6
)
}
}
impl PartialEq for Interface {
fn eq(&self, other: &Self) -> bool {
self.iface.index == other.iface.index
&& self.iface.name == other.iface.name
&& self.iface.flags == other.iface.flags
&& self.iface.mac_addr.as_ref().map(|a| a.octets())
== other.iface.mac_addr.as_ref().map(|a| a.octets())
}
}
impl Eq for Interface {}
impl Interface {
pub fn is_up(&self) -> bool {
is_up(&self.iface)
}
pub fn name(&self) -> &str {
&self.iface.name
}
pub fn addrs(&self) -> impl Iterator<Item = IpNet> + '_ {
self.iface.ipv4.iter().cloned().map(IpNet::V4).chain(
self.iface
.ipv6
.iter()
.zip(self.iface.ipv6_scope_ids.iter())
.zip(self.iface.ipv6_addr_flags.iter())
.map(|((net, scope_id), flags)| IpNet::V6 {
net: *net,
scope_id: *scope_id,
flags: *flags,
}),
)
}
pub(crate) fn fake() -> Self {
use std::net::Ipv4Addr;
use netdev::{
NetworkDevice,
prelude::{InterfaceType, MacAddr, OperState},
};
Self {
iface: netdev::Interface {
index: 2,
name: String::from("wifi0"),
friendly_name: None,
description: None,
if_type: InterfaceType::Ethernet,
mac_addr: Some(MacAddr::new(2, 3, 4, 5, 6, 7)),
ipv4: vec![Ipv4Net::new(Ipv4Addr::new(192, 168, 0, 189), 24).unwrap()],
ipv6: vec![],
flags: 69699,
transmit_speed: None,
receive_speed: None,
gateway: Some(NetworkDevice {
mac_addr: MacAddr::new(2, 3, 4, 5, 6, 8),
ipv4: vec![Ipv4Addr::from([192, 168, 0, 1])],
ipv6: vec![],
}),
dns_servers: vec![],
default: false,
ipv6_scope_ids: vec![],
ipv6_addr_flags: vec![],
stats: None,
mtu: None,
oper_state: OperState::Up,
},
}
}
}
#[derive(Clone, Debug)]
pub enum IpNet {
V4(Ipv4Net),
V6 {
net: Ipv6Net,
scope_id: u32,
flags: Ipv6AddrFlags,
},
}
impl PartialEq for IpNet {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(IpNet::V4(a), IpNet::V4(b)) => {
a.addr() == b.addr()
&& a.prefix_len() == b.prefix_len()
&& a.netmask() == b.netmask()
}
(
IpNet::V6 {
net: net_a,
scope_id: scope_id_a,
flags: flags_a,
},
IpNet::V6 {
net: net_b,
scope_id: scope_id_b,
flags: flags_b,
},
) => {
net_a.addr() == net_b.addr()
&& net_a.prefix_len() == net_b.prefix_len()
&& net_a.netmask() == net_b.netmask()
&& scope_id_a == scope_id_b
&& flags_a == flags_b
}
_ => false,
}
}
}
impl Eq for IpNet {}
impl IpNet {
pub fn addr(&self) -> IpAddr {
match self {
IpNet::V4(a) => IpAddr::V4(a.addr()),
IpNet::V6 { net, .. } => IpAddr::V6(net.addr()),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct State {
pub interfaces: HashMap<String, Interface>,
pub local_addresses: LocalAddresses,
pub have_v6: bool,
pub have_v4: bool,
pub is_expensive: bool,
pub default_route_interface: Option<String>,
pub last_unsuspend: Option<Instant>,
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut ifaces: Vec<_> = self.interfaces.values().collect();
ifaces.sort_by_key(|iface| iface.iface.index);
for iface in ifaces {
write!(f, "{iface}")?;
if let Some(ref default_if) = self.default_route_interface
&& iface.name() == default_if
{
write!(f, " (default)")?;
}
if f.alternate() {
writeln!(f)?;
} else {
write!(f, "; ")?;
}
}
Ok(())
}
}
impl State {
pub async fn new() -> Self {
let mut interfaces = HashMap::new();
let mut have_v6 = false;
let mut have_v4 = false;
let ifaces = netdev::interface::get_interfaces();
let local_addresses = LocalAddresses::from_raw_interfaces(&ifaces);
for mut iface in ifaces {
iface.ipv4.sort();
iface.dns_servers.sort();
sort_ipv6(&mut iface);
let ni = Interface { iface };
let if_up = ni.is_up();
let name = ni.iface.name.clone();
let pfxs: Vec<_> = ni.addrs().collect();
if if_up {
for pfx in &pfxs {
if pfx.addr().is_loopback() {
continue;
}
have_v6 |= is_usable_v6(&pfx.addr());
have_v4 |= is_usable_v4(&pfx.addr());
}
}
interfaces.insert(name, ni);
}
let default_route_interface = default_route_interface().await;
State {
interfaces,
local_addresses,
have_v4,
have_v6,
is_expensive: false,
default_route_interface,
last_unsuspend: None,
}
}
pub fn fake() -> Self {
let fake = Interface::fake();
let ifname = fake.iface.name.clone();
Self {
interfaces: [(ifname.clone(), fake)].into_iter().collect(),
local_addresses: LocalAddresses::from_raw_interfaces(&[]),
have_v6: true,
have_v4: true,
is_expensive: false,
default_route_interface: Some(ifname),
last_unsuspend: None,
}
}
pub fn is_major_change(&self, old: &State) -> bool {
if self.have_v6 != old.have_v6
|| self.have_v4 != old.have_v4
|| self.is_expensive != old.is_expensive
|| self.default_route_interface != old.default_route_interface
{
return true;
}
for (iname, i) in &old.interfaces {
if !is_interesting_interface(i.name()) {
continue;
}
let Some(i2) = self.interfaces.get(iname) else {
return true;
};
if i != i2 || !prefixes_major_equal(i.addrs(), i2.addrs()) {
return true;
}
}
for (iname, i) in &self.interfaces {
if !is_interesting_interface(i.name()) {
continue;
}
if !old.interfaces.contains_key(iname) {
return true;
}
}
false
}
}
fn sort_ipv6(iface: &mut netdev::Interface) {
let n = iface.ipv6.len();
debug_assert_eq!(n, iface.ipv6_scope_ids.len());
debug_assert_eq!(n, iface.ipv6_addr_flags.len());
let mut keys: Vec<_> = (0..n).collect();
keys.sort_by_key(|x| &iface.ipv6[*x]);
iface.ipv6 = keys.iter().map(|&i| iface.ipv6[i]).collect();
iface.ipv6_scope_ids = keys.iter().map(|&i| iface.ipv6_scope_ids[i]).collect();
iface.ipv6_addr_flags = keys.iter().map(|&i| iface.ipv6_addr_flags[i]).collect();
}
fn is_usable_v4(ip: &IpAddr) -> bool {
if !ip.is_ipv4() || ip.is_loopback() {
return false;
}
true
}
fn is_usable_v6(ip: &IpAddr) -> bool {
match ip {
IpAddr::V6(ip) => {
let mask: u16 = 0b1110_0000_0000_0000;
let base: u16 = 0x2000;
let segment1 = ip.segments()[0];
if (base & mask) == (segment1 & mask) {
return true;
}
is_private_v6(ip)
}
IpAddr::V4(_) => false,
}
}
#[derive(Debug, Clone)]
pub struct DefaultRouteDetails {
pub interface_name: String,
}
impl DefaultRouteDetails {
pub async fn new() -> Option<Self> {
default_route().await
}
}
pub async fn default_route_interface() -> Option<String> {
DefaultRouteDetails::new().await.map(|v| v.interface_name)
}
#[derive(Debug, Clone)]
pub struct HomeRouter {
pub gateway: IpAddr,
pub my_ip: Option<IpAddr>,
}
impl HomeRouter {
pub fn new() -> Option<Self> {
let gateway = Self::get_default_gateway()?;
let my_ip = netdev::net::ip::get_local_ipaddr();
Some(HomeRouter { gateway, my_ip })
}
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "macos",
target_os = "ios"
))]
fn get_default_gateway() -> Option<IpAddr> {
bsd::likely_home_router()
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
fn get_default_gateway() -> Option<IpAddr> {
let gateway = netdev::get_default_gateway().ok()?;
gateway
.ipv4
.iter()
.cloned()
.map(IpAddr::V4)
.chain(gateway.ipv6.iter().cloned().map(IpAddr::V6))
.next()
}
}
#[cfg(not(wasm_browser))]
fn prefixes_major_equal(a: impl Iterator<Item = IpNet>, b: impl Iterator<Item = IpNet>) -> bool {
fn is_interesting(p: &IpNet) -> bool {
let a = p.addr();
if is_link_local(a) || a.is_loopback() || a.is_multicast() {
return false;
}
true
}
let mut a = a.filter(is_interesting);
let mut b = b.filter(is_interesting);
loop {
match (a.next(), b.next()) {
(None, None) => return true,
(Some(a), Some(b)) if a == b => continue,
_ => return false,
}
}
}
#[cfg(test)]
mod tests {
use std::net::Ipv6Addr;
use super::*;
#[test]
fn test_is_major_change_identical() {
let a = State::fake();
let b = State::fake();
assert!(!a.is_major_change(&b));
}
#[test]
fn test_is_major_change_new_interface_added() {
let old = State::fake();
let mut new = State::fake();
let mut iface = Interface::fake();
iface.iface.index = 10;
iface.iface.name = "eth1".to_string();
new.interfaces.insert("eth1".to_string(), iface);
assert!(new.is_major_change(&old));
}
#[test]
fn test_is_major_change_interface_removed() {
let old = State::fake();
let mut new = State::fake();
new.interfaces.clear();
assert!(new.is_major_change(&old));
}
#[tokio::test]
async fn test_default_route() {
let default_route = DefaultRouteDetails::new()
.await
.expect("missing default route");
println!("default_route: {default_route:#?}");
}
#[tokio::test]
async fn test_likely_home_router() {
let home_router = HomeRouter::new().expect("missing home router");
println!("home router: {home_router:#?}");
}
#[test]
fn test_prefixes_major_equal() {
use std::net::Ipv4Addr;
let a1 = IpNet::V4(Ipv4Net::new(Ipv4Addr::new(192, 168, 0, 1), 24).unwrap());
let a2 = IpNet::V4(Ipv4Net::new(Ipv4Addr::new(10, 0, 0, 1), 8).unwrap());
let a3 = IpNet::V4(Ipv4Net::new(Ipv4Addr::new(172, 16, 0, 1), 16).unwrap());
assert!(prefixes_major_equal(
vec![a1.clone(), a2.clone()].into_iter(),
vec![a1.clone(), a2.clone()].into_iter(),
));
assert!(prefixes_major_equal(std::iter::empty(), std::iter::empty(),));
assert!(!prefixes_major_equal(
vec![a1.clone()].into_iter(),
vec![a2.clone()].into_iter(),
));
assert!(!prefixes_major_equal(
vec![a1.clone(), a2.clone(), a3.clone()].into_iter(),
vec![a1.clone(), a2.clone()].into_iter(),
));
assert!(!prefixes_major_equal(
vec![a1.clone(), a2.clone()].into_iter(),
vec![a1.clone(), a2.clone(), a3.clone()].into_iter(),
));
}
#[test]
fn test_sort_ipv6() {
let addr_a = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
let addr_b = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2);
let addr_c = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
let net_a = Ipv6Net::new(addr_a, 64).unwrap();
let net_b = Ipv6Net::new(addr_b, 64).unwrap();
let net_c = Ipv6Net::new(addr_c, 64).unwrap();
let flags_a = Ipv6AddrFlags {
permanent: true,
..Default::default()
};
let flags_b = Ipv6AddrFlags {
temporary: true,
..Default::default()
};
let flags_c = Ipv6AddrFlags {
deprecated: true,
..Default::default()
};
let mut iface = Interface::fake().iface;
iface.ipv6 = vec![net_c, net_b, net_a];
iface.ipv6_scope_ids = vec![30, 20, 10];
iface.ipv6_addr_flags = vec![flags_c, flags_b, flags_a];
sort_ipv6(&mut iface);
assert_eq!(iface.ipv6, vec![net_a, net_b, net_c]);
assert_eq!(iface.ipv6_scope_ids, vec![10, 20, 30]);
assert!(iface.ipv6_addr_flags[0].permanent);
assert!(iface.ipv6_addr_flags[1].temporary);
assert!(iface.ipv6_addr_flags[2].deprecated);
}
#[test]
fn test_is_usable_v6() {
let loopback = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0x1);
assert!(!is_usable_v6(&loopback.into()));
let link_local = Ipv6Addr::new(0xfe80, 0, 0, 0, 0xcbc9, 0x6aff, 0x5b07, 0x4a9e);
assert!(!is_usable_v6(&link_local.into()));
let relay_use1 = Ipv6Addr::new(0x2a01, 0x4ff, 0xf0, 0xc4a1, 0, 0, 0, 0x1);
assert!(is_usable_v6(&relay_use1.into()));
let random_2603 = Ipv6Addr::new(0x2603, 0x3ff, 0xf1, 0xc3aa, 0x1, 0x2, 0x3, 0x1);
assert!(is_usable_v6(&random_2603.into()));
}
}