network interfaces work

This commit is contained in:
John Smith 2021-12-30 23:24:17 -05:00
parent cf2acc4bd5
commit 7ba6748cd2
19 changed files with 975 additions and 164 deletions

123
Cargo.lock generated
View File

@ -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",

View File

@ -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 }

View File

@ -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 = {

View File

@ -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());

View File

@ -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::*;

View File

@ -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<IfAddr>,
}
#[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<Ipv4Addr> {
for x in self.addrs.iter() {
if let IfAddr::V4(a) = x {
return Some(a.ip);
}
}
None
}
pub fn primary_ipv6(&self) -> Option<Ipv6Addr> {
for x in self.addrs.iter() {
if let IfAddr::V6(a) = x {
return Some(a.ip);
}
}
None
}
}
pub struct NetworkInterfaces {
valid: bool,
interfaces: BTreeMap<String, NetworkInterface>,
}
#[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<bool, String> {
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<String, NetworkInterface> {
self.interfaces.iter()
}
}

View File

@ -0,0 +1,19 @@
use super::*;
pub struct PlatformSupportApple {
//
}
impl PlatformSupportApple {
pub fn new() -> Result<Self, String> {
Ok(PlatformSupportApple {})
}
pub async fn get_interfaces(
&mut self,
interfaces: &mut BTreeMap<String, NetworkInterface>,
) -> Result<(), String> {
//
Ok(())
}
}

View File

@ -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)

View File

@ -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<ProcNetIpv6RouteEntry>,
proc_net_route: Vec<ProcNetRouteEntry>,
}
impl PlatformSupport {
fn parse_proc_net_ipv6_route() -> Result<Vec<ProcNetIpv6RouteEntry>, Error> {
let file = File::open("/proc/net/ipv6_route")?;
let reader = BufReader::new(file);
let mut ipv6_route: Vec<ProcNetIpv6RouteEntry> = 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<Vec<ProcNetRouteEntry>, Error> {
let file = File::open("/proc/net/route")?;
let reader = BufReader::new(file);
let mut route: Vec<ProcNetRouteEntry> = 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<Self, Error> {
// 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,
}
}
}

View File

@ -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<IpAddr> {
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<Ipv4Addr>,
}
/// 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<Ipv6Addr>,
}
/// 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<InterfaceAddress>,
}
#[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<Ipv4Addr> {
// see if we have a non-dynamic address to use first
let mut best_dynamic: Option<Ipv4Addr> = 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<Ipv6Addr> {
let mut best_dynamic: Option<Ipv6Addr> = 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<String, NetworkInterface>,
}
#[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<bool, String> {
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<String, NetworkInterface> {
self.interfaces.iter()
}
}

View File

@ -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<String, String> {
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<u32>,
}
impl PlatformSupportNetlink {
pub fn new() -> Result<Self, String> {
// Get the netlink connection
let (connection, handle, _) = new_connection_with_socket::<SmolSocket>()
.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<InterfaceFlags, String> {
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<InterfaceAddress> {
// 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<InterfaceAddress> {
// 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<String, NetworkInterface>,
) -> 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::<u32, String>::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(())
}
}

View File

@ -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
}
}
}

View File

@ -0,0 +1,19 @@
use super::*;
pub struct PlatformSupportWindows {
//
}
impl PlatformSupportWindows {
pub fn new() -> Result<Self, String> {
Ok(PlatformSupportWindows {})
}
pub async fn get_interfaces(
&mut self,
interfaces: &mut BTreeMap<String, NetworkInterface>,
) -> Result<(), String> {
//
Ok(())
}
}

View File

@ -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);

View File

@ -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 {

View File

@ -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 {

View File

@ -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)
}
}
}

View File

@ -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();
});
}

View File

@ -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(