diff --git a/Cargo.lock b/Cargo.lock index 940fb43a..6d6af240 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1668,6 +1668,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "ifstructs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b24d770f92a5ea876a33851b16553f21985bb83e7fe8e7e1f596ad75545e9581" +dependencies = [ + "cfg-if 0.1.10", + "libc", +] + [[package]] name = "impl-codec" version = "0.5.1" @@ -2102,6 +2112,71 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" +[[package]] +name = "netlink-packet-core" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8349128e95f5dabcb8a18587ad06b3ca7993e90c0c360b4a2abac0313ebce727" +dependencies = [ + "anyhow", + "byteorder", + "libc", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb5d54077de7c0904111e1d19b661b8cfccbc23d9ce5b6dbcc7362721e6e552" +dependencies = [ + "anyhow", + "bitflags", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a008a56eceb0cab06739c7f37f15bda27f1147a14d0e7136e8c913b94f1441d" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror", +] + +[[package]] +name = "netlink-proto" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "073885f70c1d54fdc6148075e8e38a5e8a28179f59de5bd0fc6277cae4fec95a" +dependencies = [ + "bytes 1.1.0", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "tokio", +] + +[[package]] +name = "netlink-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed51a4602bb956eefef0ebc15f478bf9732fa3cc706e0a37112e654f41c5b92c" +dependencies = [ + "async-io", + "bytes 1.1.0", + "futures", + "libc", + "log", +] + [[package]] name = "nix" version = "0.17.0" @@ -2115,6 +2190,19 @@ dependencies = [ "void", ] +[[package]] +name = "nix" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + [[package]] name = "nix" version = "0.23.0" @@ -2421,6 +2509,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "paste" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -2831,6 +2925,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "rtnetlink" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa584f57f271d3fbd9f59503b090a0410a531c8cc272143669bf136c62ef409d" +dependencies = [ + "async-global-executor", + "futures", + "log", + "netlink-packet-route", + "netlink-proto", + "nix 0.22.0", + "thiserror", +] + [[package]] name = "rusqlite" version = "0.26.1" @@ -3432,6 +3541,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "tokio" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +dependencies = [ + "bytes 1.1.0", + "memchr", + "pin-project-lite", +] + [[package]] name = "toml" version = "0.5.8" @@ -3656,6 +3776,7 @@ dependencies = [ "hashbrown", "hex", "if-addrs", + "ifstructs", "jni", "jni-sys", "js-sys", @@ -3663,6 +3784,7 @@ dependencies = [ "keyvaluedb-sqlite", "keyvaluedb-web", "lazy_static", + "libc", "log", "lru", "maplit", @@ -3673,6 +3795,7 @@ dependencies = [ "once_cell", "parking_lot 0.11.2", "rand 0.7.3", + "rtnetlink", "rusqlite", "rust-fsm", "rustls", diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index 63d5149d..d9f260f9 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -67,6 +67,7 @@ async_executors = { version = "^0", features = [ "async_std" ]} socket2 = "^0" bugsalot = "^0" chrono = "^0" +libc = "^0" # Dependencies for WASM builds only [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -102,7 +103,7 @@ features = [ 'Window', ] -# Dependencies specifically for Android +# Dependencies for Android [target.'cfg(target_os = "android")'.dependencies] jni = "^0" jni-sys = "^0" @@ -111,6 +112,15 @@ ndk-glue = { version = "^0", features = ["logger"] } android_logger = { version = "^0" } backtrace = { version = "^0" } +# Dependenices for all Unix (Linux, Android, MacOS, iOS) +[target.'cfg(unix)'.dependencies] +ifstructs = "^0" + +# Dependencies for Linux or Android +[target.'cfg(any(target_os = "android",target_os = "linux"))'.dependencies] +rtnetlink = { version = "^0", default-features = false, features = [ "smol_socket" ] } + +# Dependencies for iOS [target.'cfg(target_os = "ios")'.dependencies] simplelog = { version = "^0", optional = true } backtrace = { version = "^0", optional = true } diff --git a/veilid-core/src/intf/native/network/mod.rs b/veilid-core/src/intf/native/network/mod.rs index 40697408..6067b8b0 100644 --- a/veilid-core/src/intf/native/network/mod.rs +++ b/veilid-core/src/intf/native/network/mod.rs @@ -1061,7 +1061,9 @@ impl Network { info!("starting network"); // initialize interfaces - self.inner.lock().interfaces.refresh()?; + let mut interfaces = NetworkInterfaces::new(); + interfaces.refresh().await?; + self.inner.lock().interfaces = interfaces; // get protocol config let protocol_config = { diff --git a/veilid-core/src/intf/native/table_store.rs b/veilid-core/src/intf/native/table_store.rs index 04ce8adb..3bca5824 100644 --- a/veilid-core/src/intf/native/table_store.rs +++ b/veilid-core/src/intf/native/table_store.rs @@ -95,8 +95,11 @@ impl TableStore { let dbpath = Self::get_dbpath(&inner, &table_name)?; let cfg = DatabaseConfig::with_columns(column_count); let db = - Database::open(dbpath, cfg).map_err(|e| format!("failed to open tabledb: {}", e))?; - + Database::open(&dbpath, cfg).map_err(|e| format!("failed to open tabledb: {}", e))?; + info!( + "opened table store '{}' at path '{:?}' with {} columns", + name, dbpath, column_count + ); let table_db = TableDB::new(table_name.clone(), self.clone(), db); inner.opened.insert(table_name, table_db.weak_inner()); diff --git a/veilid-core/src/intf/native/utils/android/mod.rs b/veilid-core/src/intf/native/utils/android/mod.rs index 099d2a42..ffcf282f 100644 --- a/veilid-core/src/intf/native/utils/android/mod.rs +++ b/veilid-core/src/intf/native/utils/android/mod.rs @@ -1,6 +1,8 @@ -mod android_get_if_addrs; +// xxx : support for android older than API 24, if we need it someday +//mod android_get_if_addrs; +//pub use android_get_if_addrs::*; + mod get_directories; -pub use android_get_if_addrs::*; pub use get_directories::*; use crate::xx::*; diff --git a/veilid-core/src/intf/native/utils/network_interfaces.rs b/veilid-core/src/intf/native/utils/network_interfaces.rs deleted file mode 100644 index 99b856e7..00000000 --- a/veilid-core/src/intf/native/utils/network_interfaces.rs +++ /dev/null @@ -1,108 +0,0 @@ -#[cfg(target_os = "android")] -pub use super::android::*; -use crate::xx::*; -#[cfg(not(target_os = "android"))] -pub use if_addrs::*; - -#[derive(PartialEq, Eq, Clone)] -pub struct NetworkInterface { - name: String, - is_loopback: bool, - addrs: Vec, -} - -#[allow(dead_code)] -impl NetworkInterface { - pub fn new(name: String, is_loopback: bool) -> Self { - Self { - name, - is_loopback, - addrs: Vec::new(), - } - } - pub fn name(&self) -> String { - self.name.clone() - } - pub fn is_loopback(&self) -> bool { - self.is_loopback - } - pub fn primary_ipv4(&self) -> Option { - for x in self.addrs.iter() { - if let IfAddr::V4(a) = x { - return Some(a.ip); - } - } - None - } - pub fn primary_ipv6(&self) -> Option { - for x in self.addrs.iter() { - if let IfAddr::V6(a) = x { - return Some(a.ip); - } - } - None - } -} - -pub struct NetworkInterfaces { - valid: bool, - interfaces: BTreeMap, -} - -#[allow(dead_code)] -impl NetworkInterfaces { - pub fn new() -> Self { - Self { - valid: false, - interfaces: BTreeMap::new(), - } - } - pub fn is_valid(&self) -> bool { - self.valid - } - pub fn clear(&mut self) { - self.interfaces.clear(); - self.valid = false; - } - // returns Ok(false) if refresh had no changes, Ok(true) if changes were present - pub fn refresh(&mut self) -> Result { - self.valid = false; - - let last_interfaces = core::mem::take(&mut self.interfaces); - - let mut intfs = match get_if_addrs() { - Err(e) => { - return Err(format!("failed to refresh network interfaces: {}", e)); - } - Ok(v) => v, - }; - intfs.sort(); - - // debug!("{} interfaces found", intfs.len()); - for intf in intfs { - // trace!("interface {} at {}", &intf.name, &intf.addr.ip()); - let ni = match self.interfaces.get_mut(&intf.name) { - None => { - self.interfaces.insert( - intf.name.clone(), - NetworkInterface::new(intf.name.clone(), intf.is_loopback()), - ); - self.interfaces.get_mut(&intf.name).unwrap() - } - Some(v) => v, - }; - - ni.addrs.push(intf.addr.clone()); - } - - self.valid = true; - - Ok(last_interfaces != self.interfaces) - } - pub fn len(&self) -> usize { - self.interfaces.len() - } - pub fn iter(&self) -> std::collections::btree_map::Iter { - self.interfaces.iter() - } -} diff --git a/veilid-core/src/intf/native/utils/network_interfaces/apple.rs b/veilid-core/src/intf/native/utils/network_interfaces/apple.rs new file mode 100644 index 00000000..ef544b54 --- /dev/null +++ b/veilid-core/src/intf/native/utils/network_interfaces/apple.rs @@ -0,0 +1,19 @@ +use super::*; + +pub struct PlatformSupportApple { + // +} + +impl PlatformSupportApple { + pub fn new() -> Result { + Ok(PlatformSupportApple {}) + } + + pub async fn get_interfaces( + &mut self, + interfaces: &mut BTreeMap, + ) -> Result<(), String> { + // + Ok(()) + } +} diff --git a/veilid-core/src/intf/native/utils/android/android_get_if_addrs.rs b/veilid-core/src/intf/native/utils/network_interfaces/deprecated/old_android_get_if_addrs.rs similarity index 83% rename from veilid-core/src/intf/native/utils/android/android_get_if_addrs.rs rename to veilid-core/src/intf/native/utils/network_interfaces/deprecated/old_android_get_if_addrs.rs index 667fae2b..099eaf0f 100644 --- a/veilid-core/src/intf/native/utils/android/android_get_if_addrs.rs +++ b/veilid-core/src/intf/native/utils/network_interfaces/deprecated/old_android_get_if_addrs.rs @@ -1,54 +1,8 @@ use super::*; use crate::xx::*; -pub use if_addrs::{IfAddr, Ifv4Addr, Ifv6Addr, Interface}; use jni::objects::JValue; use std::io; -fn get_netmask_from_prefix_length_v4(out: &mut [u8; 4], mut plen: i16) { - for n in 0..4 { - out[n] = if plen >= 8 { - plen -= 8; - 255u8 - } else if plen <= 0 { - 0u8 - } else { - let v = 255u8 << (8 - plen); - plen = 0; - v - } - } -} -fn get_netmask_from_prefix_length_v6(out: &mut [u8; 16], mut plen: i16) { - for n in 0..16 { - out[n] = if plen >= 8 { - plen -= 8; - 255u8 - } else if plen == 0 { - 0u8 - } else { - let v = 255u8 << (8 - plen); - plen = 0; - v - } - } -} - -fn convert_to_unsigned_4(x: [i8; 4]) -> [u8; 4] { - let mut out: [u8; 4] = [0u8; 4]; - for i in 0..4 { - out[i] = x[i] as u8; - } - out -} - -fn convert_to_unsigned_16(x: [i8; 16]) -> [u8; 16] { - let mut out: [u8; 16] = [0u8; 16]; - for i in 0..16 { - out[i] = x[i] as u8; - } - out -} - macro_rules! call_method_checked { ($env:expr, $obj:expr, $name:expr, $sig:expr, $args:expr, $kind:ident) => { $env.call_method($obj, $name, $sig, $args) diff --git a/veilid-core/src/intf/native/utils/network_interfaces/deprecated/old_linux.rs b/veilid-core/src/intf/native/utils/network_interfaces/deprecated/old_linux.rs new file mode 100644 index 00000000..d6e152d2 --- /dev/null +++ b/veilid-core/src/intf/native/utils/network_interfaces/deprecated/old_linux.rs @@ -0,0 +1,232 @@ +#![allow(dead_code)] +use super::*; +use crate::xx::*; +use hex::FromHex; +use if_addrs::{IfAddr, Ifv4Addr, Ifv6Addr}; +use std::fs::File; +use std::io::{BufRead, BufReader, Error, ErrorKind}; +use std::net::{Ipv4Addr, Ipv6Addr}; + +#[derive(Debug)] +struct ProcNetIpv6RouteEntry { + dest_network: Ipv6Addr, + dest_prefix: u8, + src_network: Ipv6Addr, + src_prefix: u8, + next_hop: Ipv6Addr, + metric: u32, + ref_count: u32, + use_count: u32, + flags: u32, + intf_name: String, +} + +#[derive(Debug)] +struct ProcNetRouteEntry { + iface: String, + destination: Ipv4Addr, + gateway: Ipv4Addr, + flags: u16, + ref_count: u32, + use_count: u32, + metric: u32, + mask: Ipv4Addr, + mtu: u32, + window: u32, + irtt: u32, +} + +#[derive(Debug)] +pub struct PlatformSupport { + proc_net_ipv6_route: Vec, + proc_net_route: Vec, +} + +impl PlatformSupport { + fn parse_proc_net_ipv6_route() -> Result, Error> { + let file = File::open("/proc/net/ipv6_route")?; + let reader = BufReader::new(file); + let mut ipv6_route: Vec = Vec::new(); + for line in reader.lines() { + let line = line?; + let line: Vec<&str> = line.split_ascii_whitespace().collect(); + if line.len() != 10 { + return Err(Error::new( + ErrorKind::InvalidData, + "Unexpected number of columns in /proc/net/ipv6_route", + )); + } + + let entry = + ProcNetIpv6RouteEntry { + dest_network: Ipv6Addr::from(<[u8; 16]>::from_hex(line[0]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse dest_network") + })?), + dest_prefix: <[u8; 1]>::from_hex(line[1]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse dest_prefix") + })?[0], + src_network: Ipv6Addr::from(<[u8; 16]>::from_hex(line[2]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse src_network") + })?), + src_prefix: <[u8; 1]>::from_hex(line[3]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse src_prefix") + })?[0], + next_hop: Ipv6Addr::from(<[u8; 16]>::from_hex(line[4]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse next_hop") + })?), + metric: u32::from_be_bytes(<[u8; 4]>::from_hex(line[5]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse metric") + })?), + ref_count: u32::from_be_bytes(<[u8; 4]>::from_hex(line[6]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse ref_count") + })?), + use_count: u32::from_be_bytes(<[u8; 4]>::from_hex(line[7]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse use_count") + })?), + flags: u32::from_be_bytes(<[u8; 4]>::from_hex(line[8]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse flags") + })?), + intf_name: String::from(line[9]), + }; + + ipv6_route.push(entry) + } + + Ok(ipv6_route) + } + + fn parse_proc_net_route() -> Result, Error> { + let file = File::open("/proc/net/route")?; + let reader = BufReader::new(file); + let mut route: Vec = Vec::new(); + let mut first = false; + for line in reader.lines() { + let line = line?; + let line: Vec<&str> = line.split_ascii_whitespace().collect(); + if line.len() != 11 { + return Err(Error::new( + ErrorKind::InvalidData, + "Unexpected number of columns in /proc/net/route", + )); + } + if first { + if line + != [ + "Iface", + "Destination", + "Gateway", + "Flags", + "RefCnt", + "Use", + "Metric", + "Mask", + "MTU", + "Window", + "IRTT", + ] + { + return Err(Error::new( + ErrorKind::InvalidData, + format!("Unexpected columns in /proc/net/route: {:?}", line), + )); + } + first = false; + continue; + } + + let entry = + ProcNetRouteEntry { + iface: String::from(line[0]), + + destination: Ipv4Addr::from(u32::from_le_bytes( + <[u8; 4]>::from_hex(line[0]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse destination") + })?, + )), + gateway: Ipv4Addr::from(u32::from_le_bytes( + <[u8; 4]>::from_hex(line[0]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse gateway") + })?, + )), + flags: u16::from_be_bytes(<[u8; 2]>::from_hex(line[8]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse flags") + })?), + ref_count: u32::from_be_bytes(<[u8; 4]>::from_hex(line[6]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse ref_count") + })?), + use_count: u32::from_be_bytes(<[u8; 4]>::from_hex(line[7]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse use_count") + })?), + metric: u32::from_be_bytes(<[u8; 4]>::from_hex(line[5]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse metric") + })?), + mask: Ipv4Addr::from(u32::from_le_bytes( + <[u8; 4]>::from_hex(line[0]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse mask") + })?, + )), + mtu: u32::from_be_bytes( + <[u8; 4]>::from_hex(line[5]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse mtu") + })?, + ), + window: u32::from_be_bytes(<[u8; 4]>::from_hex(line[5]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse window") + })?), + irtt: u32::from_be_bytes( + <[u8; 4]>::from_hex(line[5]).map_err(|_| { + Error::new(ErrorKind::InvalidData, "Unable to parse irtt") + })?, + ), + }; + + route.push(entry) + } + + Ok(route) + } + + pub fn new() -> Result { + // Read /proc/net/ipv6_route + let proc_net_ipv6_route = Self::parse_proc_net_ipv6_route().unwrap_or_default(); + // Read /proc/net/route + let proc_net_route = Self::parse_proc_net_route().unwrap_or_default(); + + trace!("proc_net_ipv6_route: {:#?}", proc_net_ipv6_route); + trace!("proc_net_route: {:#?}", proc_net_route); + + // At least one routing table must be available + if proc_net_ipv6_route.is_empty() && proc_net_route.is_empty() { + return Err(Error::new( + ErrorKind::InvalidData, + "No routing tables available", + )); + } + Ok(Self { + proc_net_ipv6_route, + proc_net_route, + }) + } + + pub fn has_default_route(&self, name: &str) -> bool { + for e in &self.proc_net_ipv6_route { + if e.intf_name == name && e.dest_prefix == 0u8 { + return true; + } + } + for e in &self.proc_net_route { + if e.iface == name && e.mask == Ipv4Addr::new(0, 0, 0, 0) { + return true; + } + } + false + } + + pub fn get_address_flags(&self, _addr: &IfAddr) -> AddressFlags { + AddressFlags { + is_temporary: false, + is_dynamic: false, + is_deprecated: false, + } + } +} diff --git a/veilid-core/src/intf/native/utils/network_interfaces/mod.rs b/veilid-core/src/intf/native/utils/network_interfaces/mod.rs new file mode 100644 index 00000000..a5bf066b --- /dev/null +++ b/veilid-core/src/intf/native/utils/network_interfaces/mod.rs @@ -0,0 +1,216 @@ +use crate::xx::*; +use crate::*; +mod tools; + +cfg_if::cfg_if! { + if #[cfg(any(target_os = "linux", target_os = "android"))] { + mod netlink; + use netlink::PlatformSupportNetlink as PlatformSupport; + } else if #[cfg(target_os = "windows")] { + mod windows; + use windows::PlatformSupportWindows as PlatformSupport; + } else if #[cfg(any(target_os = "macos", target_os = "ios"))] { + mod apple; + use apple::PlatformSupportApple as PlatformSupport; + } else { + compile_error!("No network interfaces support for this platform!"); + } +} + +#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone)] +pub enum IfAddr { + V4(Ifv4Addr), + V6(Ifv6Addr), +} + +impl IfAddr { + pub fn ip(&self) -> IpAddr { + match *self { + IfAddr::V4(ref ifv4_addr) => IpAddr::V4(ifv4_addr.ip), + IfAddr::V6(ref ifv6_addr) => IpAddr::V6(ifv6_addr.ip), + } + } + pub fn netmask(&self) -> IpAddr { + match *self { + IfAddr::V4(ref ifv4_addr) => IpAddr::V4(ifv4_addr.netmask), + IfAddr::V6(ref ifv6_addr) => IpAddr::V6(ifv6_addr.netmask), + } + } + pub fn broadcast(&self) -> Option { + match *self { + IfAddr::V4(ref ifv4_addr) => ifv4_addr.broadcast.map(IpAddr::V4), + IfAddr::V6(ref ifv6_addr) => ifv6_addr.broadcast.map(IpAddr::V6), + } + } +} + +/// Details about the ipv4 address of an interface on this host. +#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone)] +pub struct Ifv4Addr { + /// The IP address of the interface. + pub ip: Ipv4Addr, + /// The netmask of the interface. + pub netmask: Ipv4Addr, + /// The broadcast address of the interface. + pub broadcast: Option, +} + +/// Details about the ipv6 address of an interface on this host. +#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone)] +pub struct Ifv6Addr { + /// The IP address of the interface. + pub ip: Ipv6Addr, + /// The netmask of the interface. + pub netmask: Ipv6Addr, + /// The broadcast address of the interface. + pub broadcast: Option, +} + +/// Some of the flags associated with an interface. +#[derive(Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy)] +pub struct InterfaceFlags { + pub is_loopback: bool, + pub is_running: bool, + pub has_default_route: bool, +} + +/// Some of the flags associated with an address. +#[derive(Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy)] +pub struct AddressFlags { + pub is_temporary: bool, + pub is_dynamic: bool, + pub is_deprecated: bool, +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct InterfaceAddress { + if_addr: IfAddr, + flags: AddressFlags, +} + +#[allow(dead_code)] +impl InterfaceAddress { + pub fn new(if_addr: IfAddr, flags: AddressFlags) -> Self { + Self { if_addr, flags } + } + + pub fn if_addr(&self) -> &IfAddr { + &self.if_addr + } + + pub fn is_temporary(&self) -> bool { + self.flags.is_temporary + } + pub fn is_dynamic(&self) -> bool { + self.flags.is_dynamic + } + pub fn is_deprecated(&self) -> bool { + self.flags.is_deprecated + } +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct NetworkInterface { + pub name: String, + pub flags: InterfaceFlags, + pub addrs: Vec, +} + +#[allow(dead_code)] +impl NetworkInterface { + pub fn new(name: String, flags: InterfaceFlags) -> Self { + Self { + name, + flags, + addrs: Vec::new(), + } + } + pub fn name(&self) -> String { + self.name.clone() + } + pub fn is_loopback(&self) -> bool { + self.flags.is_loopback + } + + pub fn is_running(&self) -> bool { + self.flags.is_running + } + + pub fn has_default_route(&self) -> bool { + self.flags.has_default_route + } + + pub fn primary_ipv4(&self) -> Option { + // see if we have a non-dynamic address to use first + let mut best_dynamic: Option = None; + for x in self.addrs.iter() { + if let IfAddr::V4(a) = x.if_addr() { + if !x.is_dynamic() { + return Some(a.ip); + } else if best_dynamic.is_none() { + best_dynamic = Some(a.ip); + } + } + } + best_dynamic + } + pub fn primary_ipv6(&self) -> Option { + let mut best_dynamic: Option = None; + for x in self.addrs.iter() { + if let IfAddr::V6(a) = x.if_addr() { + if x.is_temporary() || x.is_deprecated() { + if !x.is_dynamic() { + return Some(a.ip); + } else if best_dynamic.is_none() { + best_dynamic = Some(a.ip); + } + } + } + } + best_dynamic + } +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct NetworkInterfaces { + valid: bool, + interfaces: BTreeMap, +} + +#[allow(dead_code)] +impl NetworkInterfaces { + pub fn new() -> Self { + Self { + valid: false, + interfaces: BTreeMap::new(), + } + } + pub fn is_valid(&self) -> bool { + self.valid + } + pub fn clear(&mut self) { + self.interfaces.clear(); + self.valid = false; + } + // returns Ok(false) if refresh had no changes, Ok(true) if changes were present + pub async fn refresh(&mut self) -> Result { + self.valid = false; + + let last_interfaces = core::mem::take(&mut self.interfaces); + + let mut platform_support = PlatformSupport::new().map_err(logthru_net!())?; + platform_support + .get_interfaces(&mut self.interfaces) + .await?; + + self.valid = true; + + Ok(last_interfaces != self.interfaces) + } + pub fn len(&self) -> usize { + self.interfaces.len() + } + pub fn iter(&self) -> std::collections::btree_map::Iter { + self.interfaces.iter() + } +} diff --git a/veilid-core/src/intf/native/utils/network_interfaces/netlink.rs b/veilid-core/src/intf/native/utils/network_interfaces/netlink.rs new file mode 100644 index 00000000..296fc49a --- /dev/null +++ b/veilid-core/src/intf/native/utils/network_interfaces/netlink.rs @@ -0,0 +1,288 @@ +use super::*; + +use alloc::collections::btree_map::Entry; +use futures_util::stream::TryStreamExt; +use ifstructs::ifreq; +use libc::{ + close, if_indextoname, ioctl, socket, IFA_F_DADFAILED, IFA_F_DEPRECATED, IFA_F_PERMANENT, + IFA_F_TEMPORARY, IFA_F_TENTATIVE, IFF_LOOPBACK, IFF_RUNNING, IF_NAMESIZE, SIOCGIFFLAGS, + SOCK_DGRAM, +}; +use rtnetlink::packet::{nlas::address::Nla, AddressMessage, AF_INET, AF_INET6}; +use rtnetlink::{new_connection_with_socket, sys::SmolSocket, Handle, IpVersion}; +use std::convert::TryInto; +use std::ffi::CStr; +use std::io; +use std::os::raw::c_int; +use tools::*; + +fn get_interface_name(index: u32) -> Result { + let mut ifnamebuf = [0u8; (IF_NAMESIZE + 1)]; + if unsafe { if_indextoname(index, ifnamebuf.as_mut_ptr() as *mut i8) }.is_null() { + return Err("if_indextoname returned null".to_owned()); + } + let ifnamebuflen = ifnamebuf + .iter() + .position(|c| *c == 0u8) + .ok_or_else(|| "null not found in interface name".to_owned())?; + let ifname_str = CStr::from_bytes_with_nul(&ifnamebuf[0..=ifnamebuflen]) + .map_err(map_to_string)? + .to_str() + .map_err(map_to_string)?; + Ok(ifname_str.to_owned()) +} + +fn flags_to_address_flags(flags: u32) -> AddressFlags { + AddressFlags { + is_temporary: (flags & IFA_F_TEMPORARY) != 0, + is_dynamic: (flags & IFA_F_PERMANENT) == 0, + is_deprecated: (flags & IFA_F_DEPRECATED) != 0, + } +} + +pub struct PlatformSupportNetlink { + _connection_jh: intf::JoinHandle<()>, + handle: Handle, + default_route_interfaces: BTreeSet, +} + +impl PlatformSupportNetlink { + pub fn new() -> Result { + // Get the netlink connection + let (connection, handle, _) = new_connection_with_socket::() + .map_err(map_to_string) + .map_err(logthru_net!(error))?; + + // Spawn a connection handler + let _connection_jh = intf::spawn(connection); + + Ok(PlatformSupportNetlink { + _connection_jh, + handle, + default_route_interfaces: BTreeSet::new(), + }) + } + + // Figure out which interfaces have default routes + async fn refresh_default_route_interfaces(&mut self) -> Result<(), String> { + self.default_route_interfaces.clear(); + let mut routesv4 = self.handle.route().get(IpVersion::V4).execute(); + while let Some(routev4) = routesv4.try_next().await.unwrap_or_default() { + if let Some(index) = routev4.input_interface() { + if let Some((dest, prefix)) = routev4.destination_prefix() { + if prefix == 0 && dest.is_unspecified() { + self.default_route_interfaces.insert(index); + } + } + } + } + let mut routesv6 = self.handle.route().get(IpVersion::V6).execute(); + while let Some(routev6) = routesv6.try_next().await.unwrap_or_default() { + if let Some(index) = routev6.input_interface() { + if let Some((dest, prefix)) = routev6.destination_prefix() { + if prefix == 0 && dest.is_unspecified() { + self.default_route_interfaces.insert(index); + } + } + } + } + Ok(()) + } + + fn get_interface_flags(&self, index: u32, ifname: &str) -> Result { + let mut req = ifreq::from_name(ifname).map_err(map_to_string)?; + + let sock = unsafe { socket(AF_INET as i32, SOCK_DGRAM, 0) }; + if sock < 0 { + return Err(io::Error::last_os_error()).map_err(map_to_string); + } + + let res = unsafe { ioctl(sock, SIOCGIFFLAGS, &mut req) }; + unsafe { close(sock) }; + if res < 0 { + return Err(io::Error::last_os_error()).map_err(map_to_string); + } + + let flags = req.get_flags() as c_int; + + Ok(InterfaceFlags { + is_loopback: (flags & IFF_LOOPBACK) != 0, + is_running: (flags & IFF_RUNNING) != 0, + has_default_route: self.default_route_interfaces.contains(&index), + }) + } + + fn process_address_message_v4(msg: AddressMessage) -> Option { + // Get ip address + let ip = msg.nlas.iter().find_map(|nla| { + if let Nla::Address(a) = nla { + let a: Option<[u8; 4]> = a.clone().try_into().ok(); + a.map(Ipv4Addr::from) + } else { + None + } + })?; + + // get netmask + let plen = msg.header.prefix_len as i16; + let mut netmask = [0u8; 4]; + get_netmask_from_prefix_length_v4(&mut netmask, plen); + let netmask = Ipv4Addr::from(netmask); + + // get broadcast address + let broadcast = msg.nlas.iter().find_map(|nla| { + if let Nla::Broadcast(b) = nla { + let b: Option<[u8; 4]> = b.clone().try_into().ok(); + b.map(Ipv4Addr::from) + } else { + None + } + }); + + // get address flags + let flags = msg + .nlas + .iter() + .find_map(|nla| { + if let Nla::Flags(f) = nla { + Some(*f) + } else { + None + } + }) + .unwrap_or(msg.header.flags as u32); + + Some(InterfaceAddress::new( + IfAddr::V4(Ifv4Addr { + ip, + /// The netmask of the interface. + netmask, + /// The broadcast address of the interface. + broadcast, + }), + flags_to_address_flags(flags), + )) + } + + fn process_address_message_v6(msg: AddressMessage) -> Option { + // Get ip address + let ip = msg.nlas.iter().find_map(|nla| { + if let Nla::Address(a) = nla { + let a: Option<[u8; 16]> = a.clone().try_into().ok(); + a.map(Ipv6Addr::from) + } else { + None + } + })?; + + // get netmask + let plen = msg.header.prefix_len as i16; + let mut netmask = [0u8; 16]; + get_netmask_from_prefix_length_v6(&mut netmask, plen); + let netmask = Ipv6Addr::from(netmask); + + // get address flags + let flags = msg + .nlas + .iter() + .find_map(|nla| { + if let Nla::Flags(f) = nla { + Some(*f) + } else { + None + } + }) + .unwrap_or(msg.header.flags as u32); + + // Skip addresses going through duplicate address detection, or ones that have failed it + if ((flags & IFA_F_TENTATIVE) != 0) || ((flags & IFA_F_DADFAILED) != 0) { + return None; + } + + Some(InterfaceAddress::new( + IfAddr::V6(Ifv6Addr { + ip, + /// The netmask of the interface. + netmask, + /// The broadcast address of the interface. + broadcast: None, + }), + flags_to_address_flags(flags), + )) + } + + pub async fn get_interfaces( + &mut self, + interfaces: &mut BTreeMap, + ) -> Result<(), String> { + // Refresh the routes + self.refresh_default_route_interfaces().await?; + + // If we have no routes, this isn't going to work + if self.default_route_interfaces.is_empty() { + return Err("no routes available for NetworkInterfaces".to_owned()); + } + + // Ask for all the addresses we have + let mut names = BTreeMap::::new(); + let mut addresses = self.handle.address().get().execute(); + while let Some(msg) = addresses + .try_next() + .await + .map_err(map_to_string) + .map_err(logthru_net!(error))? + { + // Have we seen this interface index yet? + // Get the name from the index, cached, if we can + let ifname = match names.entry(msg.header.index) { + Entry::Vacant(v) => { + // If not, get the name for the index if we can + let ifname = match get_interface_name(msg.header.index) { + Ok(v) => v, + Err(e) => { + log_net!( + "couldn't get interface name for index {}: {}", + msg.header.index, + e + ); + continue; + } + }; + v.insert(ifname).clone() + } + Entry::Occupied(o) => o.get().clone(), + }; + + // Map the name to a NetworkInterface + if !interfaces.contains_key(&ifname) { + // If we have no NetworkInterface yet, make one + let flags = self.get_interface_flags(msg.header.index, &ifname)?; + interfaces.insert(ifname.clone(), NetworkInterface::new(ifname.clone(), flags)); + } + let intf = interfaces.get_mut(&ifname).unwrap(); + + // Process the address + let intf_addr = match msg.header.family as u16 { + AF_INET => match Self::process_address_message_v4(msg) { + Some(ia) => ia, + None => { + continue; + } + }, + AF_INET6 => match Self::process_address_message_v6(msg) { + Some(ia) => ia, + None => { + continue; + } + }, + _ => { + continue; + } + }; + + intf.addrs.push(intf_addr); + } + + Ok(()) + } +} diff --git a/veilid-core/src/intf/native/utils/network_interfaces/tools.rs b/veilid-core/src/intf/native/utils/network_interfaces/tools.rs new file mode 100644 index 00000000..3c5dff68 --- /dev/null +++ b/veilid-core/src/intf/native/utils/network_interfaces/tools.rs @@ -0,0 +1,45 @@ +pub fn convert_to_unsigned_4(x: [i8; 4]) -> [u8; 4] { + let mut out: [u8; 4] = [0u8; 4]; + for i in 0..4 { + out[i] = x[i] as u8; + } + out +} + +pub fn convert_to_unsigned_16(x: [i8; 16]) -> [u8; 16] { + let mut out: [u8; 16] = [0u8; 16]; + for i in 0..16 { + out[i] = x[i] as u8; + } + out +} + +pub fn get_netmask_from_prefix_length_v4(out: &mut [u8; 4], mut plen: i16) { + for outb in out.iter_mut() { + *outb = if plen >= 8 { + plen -= 8; + 255u8 + } else if plen <= 0 { + 0u8 + } else { + let v = 255u8 << (8 - plen); + plen = 0; + v + } + } +} + +pub fn get_netmask_from_prefix_length_v6(out: &mut [u8; 16], mut plen: i16) { + for outb in out.iter_mut() { + *outb = if plen >= 8 { + plen -= 8; + 255u8 + } else if plen == 0 { + 0u8 + } else { + let v = 255u8 << (8 - plen); + plen = 0; + v + } + } +} diff --git a/veilid-core/src/intf/native/utils/network_interfaces/windows.rs b/veilid-core/src/intf/native/utils/network_interfaces/windows.rs new file mode 100644 index 00000000..e66ac463 --- /dev/null +++ b/veilid-core/src/intf/native/utils/network_interfaces/windows.rs @@ -0,0 +1,19 @@ +use super::*; + +pub struct PlatformSupportWindows { + // +} + +impl PlatformSupportWindows { + pub fn new() -> Result { + Ok(PlatformSupportWindows {}) + } + + pub async fn get_interfaces( + &mut self, + interfaces: &mut BTreeMap, + ) -> Result<(), String> { + // + Ok(()) + } +} diff --git a/veilid-core/src/intf/wasm/table_store.rs b/veilid-core/src/intf/wasm/table_store.rs index 0a43af6c..dbec92cc 100644 --- a/veilid-core/src/intf/wasm/table_store.rs +++ b/veilid-core/src/intf/wasm/table_store.rs @@ -80,6 +80,7 @@ impl TableStore { let db = Database::open(table_name.clone(), column_count) .await .map_err(|e| format!("failed to open tabledb at: {} ({})", table_name, e))?; + info!("opened table store '{}' with table name '{:?}' with {} columns", name, table_name, column_count); let table_db = TableDB::new(table_name.clone(), self.clone(), db); diff --git a/veilid-core/src/tests/android/app/build.gradle b/veilid-core/src/tests/android/app/build.gradle index 3d1ab01c..36f620e9 100644 --- a/veilid-core/src/tests/android/app/build.gradle +++ b/veilid-core/src/tests/android/app/build.gradle @@ -8,7 +8,7 @@ android { defaultConfig { applicationId "com.veilid.veilidcore.veilidcore_android_tests" - minSdkVersion 23 + minSdkVersion 24 targetSdkVersion 30 versionCode 1 versionName "1.0" @@ -61,9 +61,10 @@ dependencies { apply plugin: 'org.mozilla.rust-android-gradle.rust-android' cargo { - module = "../../../.." + module = "../../../../../veilid-core" libname = "veilid_core" targets = ["arm64", "x86", "x86_64"] + targetDirectory = "../../../../../target" prebuiltToolchains = true profile = gradle.startParameter.taskNames.any{it.toLowerCase().contains("debug")} ? "debug" : "release" features { diff --git a/veilid-core/src/tests/android/build.gradle b/veilid-core/src/tests/android/build.gradle index e01cd252..96496236 100644 --- a/veilid-core/src/tests/android/build.gradle +++ b/veilid-core/src/tests/android/build.gradle @@ -13,7 +13,7 @@ buildscript { } plugins { - id "org.mozilla.rust-android-gradle.rust-android" version "0.8.6" + id "org.mozilla.rust-android-gradle.rust-android" version "0.9.0" } allprojects { diff --git a/veilid-core/src/tests/common/test_host_interface.rs b/veilid-core/src/tests/common/test_host_interface.rs index 1ed2e09f..578c2f39 100644 --- a/veilid-core/src/tests/common/test_host_interface.rs +++ b/veilid-core/src/tests/common/test_host_interface.rs @@ -542,13 +542,14 @@ cfg_if! { let mut interfaces = intf::utils::network_interfaces::NetworkInterfaces::new(); let count = 100; for _ in 0..count { - if let Err(e) = interfaces.refresh() { + if let Err(e) = interfaces.refresh().await { error!("error refreshing interfaces: {}", e); } } let t2 = intf::get_timestamp(); let tdiff = ((t2 - t1) as f64)/1000000.0f64; info!("running network interface test with {} iterations took {} seconds", count, tdiff); + info!("interfaces: {:#?}", interfaces) } } } diff --git a/veilid-core/src/tests/native/mod.rs b/veilid-core/src/tests/native/mod.rs index 1592c46d..266106a0 100644 --- a/veilid-core/src/tests/native/mod.rs +++ b/veilid-core/src/tests/native/mod.rs @@ -125,6 +125,8 @@ cfg_if! { cb.add_filter_ignore_str("async_std"); cb.add_filter_ignore_str("async_io"); cb.add_filter_ignore_str("polling"); + cb.add_filter_ignore_str("netlink_proto"); + cb.add_filter_ignore_str("netlink_sys"); TestLogger::init(LevelFilter::Trace, cb.build()).unwrap(); }); } diff --git a/veilid-server/src/unix.rs b/veilid-server/src/unix.rs index fc69d11b..4877c0fb 100644 --- a/veilid-server/src/unix.rs +++ b/veilid-server/src/unix.rs @@ -196,6 +196,7 @@ pub async fn main() -> Result<(), String> { cb.add_filter_ignore_str("rustls"); cb.add_filter_ignore_str("async_tungstenite"); cb.add_filter_ignore_str("tungstenite"); + cb.add_filter_ignore_str("netlink_proto"); if settingsr.logging.terminal.enabled { logs.push(TermLogger::new(