diff --git a/mgmtd/assets/beegfs-mgmtd.toml b/mgmtd/assets/beegfs-mgmtd.toml index 1be1036..c876e21 100644 --- a/mgmtd/assets/beegfs-mgmtd.toml +++ b/mgmtd/assets/beegfs-mgmtd.toml @@ -37,9 +37,35 @@ # The private key file belonging to the above certificate. # tls-key-file = "/etc/beegfs/key.pem" -# Restricts network interfaces reported to other nodes for incoming BeeMsg communication. -# The interfaces are reported in the given order. If not given, all suitable interfaces can be used. -# interfaces = ["eth0", "eth1"] +# Restricts and prioritizes network interfaces reported to other nodes for incoming BeeMsg +# communication. +# +# Accepts a list of interface/nic filters. Interfaces can be filtered by name, address and protocol +# (ipv4 or ipv6). Each filter entry has the form `[!] [|*] [|*] [|*]`, where +# protocol can be "4" or "6". Each field can be set to "*" to match any value. Stars on the right +# can be omitted. The order of the filter entries determines the priority of the interfaces as they +# should be used by other nodes for BeeMsg communication. The first entry an interface matches is +# that interfaces priority - the earlier the match, the higher the priority. Any interface that +# doesn't match any entry is not reported and will thus not be contacted by other nodes. A single +# `!` before the entry blacklists the matching interfaces - it is not reported even if a later entry +# does match it. +# +# If not given, all suitable interfaces can be used and are reported in default order. +# +# EXAMPLES: +# +# * Prefer IPv6: ["* * 6", "* * 4"] +# * IPv6 only: ["* * 6"] +# * Only the eth0 interface using IPv6: ["eth0 * 6"] +# * Prefer one IPv6 address, allow only IPv4 otherwise: ["* fd00::1", "* * 4"] +# * Deny eth0 interface, allow everything else: ["! eth0", "*"] +# +# interfaces = ["*"] + +# Prefers an interfaces ipv6 addresses over ipv4. +# By default, ipv4 addresses are preferred. If the interface filter is given, the interface +# order has higher priority than the address family, which is sorted per interface. +# interfaces_prefer_ipv6 = false # Maximum number of outgoing connections per node. # connection-limit = 12 diff --git a/mgmtd/src/bee_msg.rs b/mgmtd/src/bee_msg.rs index 37c1266..b8379ef 100644 --- a/mgmtd/src/bee_msg.rs +++ b/mgmtd/src/bee_msg.rs @@ -194,7 +194,7 @@ pub async fn notify_nodes( node_types: &'static [NodeType], msg: &M, ) { - log::trace!("NOTIFICATION to {:?}: {:?}", node_types, msg); + log::trace!("NOTIFICATION to {node_types:?}: {msg:?}"); if let Err(err) = async { for t in node_types { diff --git a/mgmtd/src/bee_msg/target.rs b/mgmtd/src/bee_msg/target.rs index 757c26e..a616045 100644 --- a/mgmtd/src/bee_msg/target.rs +++ b/mgmtd/src/bee_msg/target.rs @@ -262,7 +262,7 @@ impl HandleWithResponse for SetStorageTargetInfo { }) .await?; - log::debug!("Updated {:?} target info", node_type,); + log::debug!("Updated {node_type:?} target info"); // in the old mgmtd, a notice to refresh cap pools is sent out here if a cap pool // changed I consider this being to expensive to check here and just don't diff --git a/mgmtd/src/config.rs b/mgmtd/src/config.rs index 245228b..3f1726c 100644 --- a/mgmtd/src/config.rs +++ b/mgmtd/src/config.rs @@ -5,6 +5,7 @@ use anyhow::{Context, Result, bail}; use clap::{Parser, ValueEnum}; use log::LevelFilter; use serde::{Deserialize, Deserializer}; +use shared::nic::{self, NicFilter}; use shared::parser::{duration, integer_range}; use shared::types::{Port, QuotaId}; use std::fmt::Debug; @@ -203,14 +204,33 @@ generate_structs! { #[arg(value_name = "PATH")] tls_key_file: PathBuf = "/etc/beegfs/key.pem".into(), - /// Restricts network interfaces reported to other nodes for incoming BeeMsg communication. + /// Restricts and prioritizes network interfaces reported to other nodes for incoming BeeMsg + /// communication. /// - /// Accepts a comma separated list of interface names. They are reported in the given order. If - /// not given, all suitable interfaces can be used. + /// Accepts a comma separated list of interface/nic filters. Interfaces can be filtered by + /// name, address and protocol (ipv4 or ipv6). Each filter entry has the form `[!] [|*] + /// [|*] [|*]`, where protocol can be "4" or "6". Each field can be set to + /// "*" to match any value. Stars on the right can be omitted. The order of the filter entries + /// determines the priority of the interfaces as they should be used by other nodes for BeeMsg + /// communication. The first entry an interface matches is that interfaces priority - the + /// earlier the match, the higher the priority. Any interface that doesn't match any entry is + /// not reported and will thus not be contacted by other nodes. A single `!` before the entry + /// blacklists the matching interfaces - it is not reported even if a later entry does match it. + /// + /// If not given, all suitable interfaces can be used and are reported in default order. + /// + /// EXAMPLES: + /// + /// * Prefer IPv6: `* * 6,* * 4` + /// * IPv6 only: `* * 6` + /// * Only the eth0 interface using IPv6: `eth0 * 6` + /// * Prefer one IPv6 address, allow only IPv4 otherwise: `* fd00::1,* * 4` + /// * Deny eth0 interface, allow everything else: `! eth0,*` #[arg(long)] - #[arg(value_name = "NAMES")] + #[arg(value_name = "FILTERS")] #[arg(value_delimiter = ',')] - interfaces: Vec = vec![], + #[arg(value_parser = nic::NicFilter::parse)] + interfaces: Vec = vec![], /// Maximum number of outgoing BeeMsg connections per node. [default: 12] #[arg(long)] @@ -482,13 +502,13 @@ pub fn load_and_parse() -> Result<(Config, Vec)> { let file_config: OptionalConfig = toml::from_str(toml_config).with_context(|| "Could not parse config file")?; - info_log.push(format!("Loaded config file from {:?}", config_file)); + info_log.push(format!("Loaded config file from {config_file:?}")); config.update_from_optional(file_config); } Err(err) => { if config_file != &config.config_file { return Err(err) - .with_context(|| format!("Could not open config file at {:?}", config_file)); + .with_context(|| format!("Could not open config file at {config_file:?}")); } info_log.push("No config file found at default location, ignoring".to_string()); diff --git a/mgmtd/src/db/import_v7.rs b/mgmtd/src/db/import_v7.rs index b5cd42d..361ba25 100644 --- a/mgmtd/src/db/import_v7.rs +++ b/mgmtd/src/db/import_v7.rs @@ -374,10 +374,7 @@ fn quota(tx: &Transaction, quota_path: &Path) -> Result<()> { quota_default_limits(tx, &e.path().join("quotaDefaultLimits.store"), pool_id) .with_context(|| { - format!( - "quota default limits ({}/quotaDefaultLimits.store)", - pool_id - ) + format!("quota default limits ({pool_id}/quotaDefaultLimits.store)") })?; quota_limits( @@ -386,7 +383,7 @@ fn quota(tx: &Transaction, quota_path: &Path) -> Result<()> { pool_id, QuotaIdType::User, ) - .with_context(|| format!("quota user limits ({}/quotaUserLimits.store)", pool_id))?; + .with_context(|| format!("quota user limits ({pool_id}/quotaUserLimits.store)"))?; quota_limits( tx, @@ -394,7 +391,7 @@ fn quota(tx: &Transaction, quota_path: &Path) -> Result<()> { pool_id, QuotaIdType::Group, ) - .with_context(|| format!("quota group limits ({}/quotaGroupLimits.store)", pool_id))?; + .with_context(|| format!("quota group limits ({pool_id}/quotaGroupLimits.store)"))?; // We intentionally ignore the quota usage data - it is fetched and updated from the // nodes on a regular basis anyway. diff --git a/mgmtd/src/db/target.rs b/mgmtd/src/db/target.rs index 084f811..81596d9 100644 --- a/mgmtd/src/db/target.rs +++ b/mgmtd/src/db/target.rs @@ -331,8 +331,8 @@ mod test { assert_eq!(19, targets.len()); - assert!(targets.iter().any(|e| *e == new_target_id)); - assert!(targets.iter().any(|e| *e == 1000)); + assert!(targets.contains(&new_target_id)); + assert!(targets.contains(&1000)); }) } } diff --git a/mgmtd/src/grpc.rs b/mgmtd/src/grpc.rs index 1cf1cd5..55ab919 100644 --- a/mgmtd/src/grpc.rs +++ b/mgmtd/src/grpc.rs @@ -181,7 +181,7 @@ pub(crate) fn serve(ctx: Context, mut shutdown: RunStateHandle) -> Result<()> { builder }; - let serve_addr = SocketAddr::new("0.0.0.0".parse()?, ctx.info.user_config.grpc_port); + let serve_addr = SocketAddr::new("::".parse()?, ctx.info.user_config.grpc_port); let service = pm::management_server::ManagementServer::with_interceptor( ManagementService { ctx: ctx.clone() }, diff --git a/mgmtd/src/grpc/buddy_group.rs b/mgmtd/src/grpc/buddy_group.rs index aded3bd..9f9d898 100644 --- a/mgmtd/src/grpc/buddy_group.rs +++ b/mgmtd/src/grpc/buddy_group.rs @@ -219,7 +219,7 @@ Primary result: {:?}, Secondary result: {:?}", .await?; if execute { - log::info!("Buddy group deleted: {}", group); + log::info!("Buddy group deleted: {group}"); } Ok(pm::DeleteBuddyGroupResponse { diff --git a/mgmtd/src/grpc/node.rs b/mgmtd/src/grpc/node.rs index 9391d82..2b97adf 100644 --- a/mgmtd/src/grpc/node.rs +++ b/mgmtd/src/grpc/node.rs @@ -1,5 +1,7 @@ use super::*; use shared::bee_msg::node::RemoveNode; +use std::net::{IpAddr, Ipv6Addr}; +use std::str::FromStr; /// Delivers a list of nodes pub(crate) async fn get(ctx: Context, req: pm::GetNodesRequest) -> Result { @@ -139,7 +141,11 @@ pub(crate) async fn get(ctx: Context, req: pm::GetNodesRequest) -> Result, - pub network_addrs: Vec, + pub network_addrs: Vec, } /// Starts the management service. @@ -63,7 +63,7 @@ pub async fn start(info: StaticInfo, license: LicenseVerifier) -> Result Result Result { if affected > 0 { - log::info!("Deleted {} stale clients", affected); + log::info!("Deleted {affected} stale clients"); } } Err(err) => log::error!("Deleting stale clients failed: {err:#}"), diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5bedd37..c2ee2a8 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.85" +channel = "1.88" profile = "default" diff --git a/shared/src/conn/outgoing.rs b/shared/src/conn/outgoing.rs index 7b6dc5d..e7e2548 100644 --- a/shared/src/conn/outgoing.rs +++ b/shared/src/conn/outgoing.rs @@ -48,7 +48,7 @@ impl Pool { node_uid: Uid, msg: &M, ) -> Result { - log::trace!("REQUEST to {:?}: {:?}", node_uid, msg); + log::trace!("REQUEST to {node_uid:?}: {msg:?}"); let mut buf = self.store.pop_buf().unwrap_or_default(); @@ -58,14 +58,14 @@ impl Pool { self.store.push_buf(buf); - log::trace!("RESPONSE RECEIVED from {:?}: {:?}", node_uid, resp); + log::trace!("RESPONSE RECEIVED from {node_uid:?}: {resp:?}"); Ok(resp) } /// Sends a [Msg] to a node and does **not** receive a response. pub async fn send(&self, node_uid: Uid, msg: &M) -> Result<()> { - log::trace!("SEND to {:?}: {:?}", node_uid, msg); + log::trace!("SEND to {node_uid:?}: {msg:?}"); let mut buf = self.store.pop_buf().unwrap_or_default(); diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 0e7111e..47a679a 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -13,66 +13,7 @@ pub mod conn; #[cfg(feature = "grpc")] pub mod grpc; pub mod journald_logger; +pub mod nic; pub mod parser; pub mod run_state; pub mod types; - -use anyhow::{Result, bail}; -use std::net::IpAddr; - -#[derive(Debug, Clone)] -pub struct NetworkAddr { - pub addr: IpAddr, - pub name: String, -} - -/// Retrieve the systems available network interfaces with their addresses -/// -/// Only interfaces matching one of the given names in `filter` will be returned, unless the list -/// is empty. -pub fn ethernet_interfaces(filter: &[impl AsRef]) -> Result> { - let mut filtered_nics = vec![]; - for interface in pnet_datalink::interfaces() { - if !filter.is_empty() && !filter.iter().any(|e| interface.name == e.as_ref()) { - continue; - } - - for ip in interface.ips { - // TODO Ipv6: Remove the Ipv4 filter when protocol changes (https://github.com/ThinkParQ/beegfs-rs/issues/145) - if !ip.is_ipv4() { - continue; - } - - filtered_nics.push(NetworkAddr { - addr: ip.ip(), - name: interface.name.clone(), - }); - } - } - - // Check all filters have been used - if !filter - .iter() - .all(|e| filtered_nics.iter().any(|g| g.name == e.as_ref())) - { - bail!("At least one network interface doesn't exist"); - } - - // Sort - filtered_nics.sort_unstable_by_key(|k| { - if filter.is_empty() { - // Move loopbacks to the back - k.addr.is_loopback() as usize - } else { - // Sort by filter - filter - .iter() - .enumerate() - .find(|e| e.1.as_ref() == k.name) - .map(|e| e.0) - .unwrap_or(usize::MAX) - } - }); - - Ok(filtered_nics) -} diff --git a/shared/src/nic.rs b/shared/src/nic.rs new file mode 100644 index 0000000..027d40b --- /dev/null +++ b/shared/src/nic.rs @@ -0,0 +1,425 @@ +use crate::types::NicType; +use anyhow::{Result, anyhow}; +use serde::Deserializer; +use serde::de::{Unexpected, Visitor}; +use std::net::IpAddr; +use std::str::FromStr; + +/// Network protocol +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Protocol { + IPv4, + IPv6, +} + +impl FromStr for Protocol { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + match s { + "4" => Ok(Self::IPv4), + "6" => Ok(Self::IPv6), + s => Err(anyhow!("{s} is not a valid protocol")), + } + } +} + +/// A filter entry for matching nics +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct NicFilter { + pub invert: bool, + pub name: Option, + pub address: Option, + pub protocol: Option, + pub nic_type: Option, +} + +impl NicFilter { + const EXPECT_STR: &str = + "a nic filter in the form \"[!] [|*] [|*] [4|6|*] [tcp|rdma|*]\""; + + /// Parses a string in the form `[!] [name] [addr] [protocol] [type]` into a [NicFilter] + #[rustfmt::skip] // opt out because if let chaings are misformatted + pub fn parse_optional(input: &str) -> Option { + let mut split = input.split_whitespace().peekable(); + let mut res = Self::default(); + + if let Some(field) = split.peek() { + if *field == "!" { + res.invert = true; + split.next(); + } + } + + if let Some(field) = split.next() && field != "*" { + res.name = Some(field.to_string()); + } + + if let Some(field) = split.next() && field != "*" { + res.address = Some(field.parse().ok()?); + } + + if let Some(field) = split.next() && field != "*" { + res.protocol = Some(field.parse().ok()?); + } + + if let Some(field) = split.next() && field != "*" { + res.nic_type = Some(field.parse().ok()?); + } + + Some(res) + } + + /// Parses a string in the form `[!] [name] [addr] [protocol] [type]` into a [NicFilter] + pub fn parse(input: &str) -> Result { + Self::parse_optional(input).ok_or_else(|| anyhow!(Self::EXPECT_STR)) + } +} + +#[derive(Debug, Default)] +struct NicFilterVisitor; + +impl Visitor<'_> for NicFilterVisitor { + type Value = NicFilter; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str(NicFilter::EXPECT_STR) + } + + fn visit_str(self, input: &str) -> Result + where + E: serde::de::Error, + { + NicFilter::parse_optional(input) + .ok_or_else(|| E::invalid_value(Unexpected::Str(input), &self)) + } +} + +impl<'de> serde::Deserialize<'de> for NicFilter { + fn deserialize(de: D) -> std::result::Result + where + D: Deserializer<'de>, + { + de.deserialize_str(NicFilterVisitor) + } +} + +// NIC FILTERING AND QUERYING + +/// Returns a priority for a given nic info based on the filter list. Returns `None` if there is +/// no match or the nic is matched on a `!` entry. +fn nic_priority(filter: &[NicFilter], name: &str, ip: &IpAddr) -> Option { + // Always ignore link local addresses + if match ip { + IpAddr::V4(a) => a.is_link_local(), + IpAddr::V6(a) => a.is_unicast_link_local(), + } { + return None; + } + + if filter.is_empty() { + return Some(0); + } + + for (i, fil) in filter.iter().enumerate() { + if fil.name.as_ref().is_some_and(|e| e != name) { + continue; + } + if fil.address.as_ref().is_some_and(|e| e != ip) { + continue; + } + if fil.protocol.as_ref().is_some_and(|e| match e { + Protocol::IPv4 => ip.is_ipv6(), + Protocol::IPv6 => ip.is_ipv4(), + }) { + continue; + } + // We don't detect rdma interfaces yet + if fil.nic_type.as_ref().is_some_and(|e| match e { + NicType::Ethernet => false, + NicType::Rdma => true, + }) { + continue; + } + + if fil.invert { + return None; + } else { + return Some(i); + } + } + + None +} + +/// A local interfaces address (a "Nic" in BeeGFS terms) including a priority for manual sorting +/// priority. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Nic { + pub address: IpAddr, + pub nic_type: NicType, + pub name: String, + pub priority: usize, +} +impl PartialOrd for Nic { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Nic { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.priority + .cmp(&other.priority) + // Put loopbacks last ( = non-loopbacks first) + .then_with(|| self.address.is_loopback().cmp(&other.address.is_loopback())) + // Then prioritize ipv4 + .then_with(|| other.address.is_ipv4().cmp(&self.address.is_ipv4())) + // Then rdma interfaces + .then_with(|| self.nic_type.cmp(&other.nic_type)) + .then_with(|| self.address.cmp(&other.address)) + .then_with(|| self.name.cmp(&other.name)) + } +} + +/// Retrieve the systems available network interfaces with their addresses +/// +/// Only interfaces matching one of the given names in `filter` will be returned, unless the list +/// is empty. +pub fn query_nics(filter: &[NicFilter]) -> Result> { + let mut filtered_nics = vec![]; + + for interface in pnet_datalink::interfaces() { + for ip in interface.ips { + if let Some(priority) = nic_priority(filter, &interface.name, &ip.ip()) { + filtered_nics.push(Nic { + name: interface.name.clone(), + address: ip.ip(), + nic_type: NicType::Ethernet, + priority, + }); + } + } + } + + filtered_nics.sort(); + + Ok(filtered_nics) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_nic_filter() { + let any = NicFilter { + invert: false, + name: None, + address: None, + protocol: None, + nic_type: None, + }; + + assert_eq!(NicFilter::parse_optional("").unwrap(), any); + assert_eq!(NicFilter::parse_optional("*").unwrap(), any); + assert_eq!(NicFilter::parse_optional("* *").unwrap(), any); + assert_eq!(NicFilter::parse_optional("* * *").unwrap(), any); + assert_eq!(NicFilter::parse_optional("* * * *").unwrap(), any); + assert_eq!(NicFilter::parse_optional("* * * * *").unwrap(), any); + assert_eq!(NicFilter::parse_optional("* * * * * *").unwrap(), any); + + // Single field + assert_eq!( + NicFilter::parse_optional("eth0").unwrap(), + NicFilter { + name: Some("eth0".into()), + ..Default::default() + } + ); + assert_eq!( + NicFilter::parse_optional("* 127.0.0.1").unwrap(), + NicFilter { + address: Some("127.0.0.1".parse().unwrap()), + ..Default::default() + } + ); + assert_eq!( + NicFilter::parse_optional("* * 4").unwrap(), + NicFilter { + protocol: Some(Protocol::IPv4), + ..Default::default() + } + ); + assert_eq!( + NicFilter::parse_optional("* * * tcp").unwrap(), + NicFilter { + nic_type: Some(NicType::Ethernet), + ..Default::default() + } + ); + + // Additional * + assert_eq!( + NicFilter::parse_optional("* * * rdma * * * *").unwrap(), + NicFilter { + nic_type: Some(NicType::Rdma), + ..Default::default() + } + ); + + // Multiple fields + assert_eq!( + NicFilter::parse_optional("eth0 fd00::1 6 rdma").unwrap(), + NicFilter { + name: Some("eth0".into()), + address: Some("fd00::1".parse().unwrap()), + protocol: Some(Protocol::IPv6), + nic_type: Some(NicType::Rdma), + ..Default::default() + } + ); + + // Inverted + assert_eq!( + NicFilter::parse_optional("! * fd00::1").unwrap(), + NicFilter { + invert: true, + address: Some("fd00::1".parse().unwrap()), + ..Default::default() + } + ); + assert_eq!( + NicFilter::parse_optional("!eth0").unwrap(), + NicFilter { + name: Some("!eth0".into()), + ..Default::default() + } + ); + } + + #[test] + fn match_nic_filter() { + use super::*; + + let f_prefer_ipv6 = &[ + NicFilter::parse("* * 6").unwrap(), + NicFilter::parse("* * 4").unwrap(), + ]; + assert_eq!( + nic_priority(f_prefer_ipv6, "eth0", &"127.0.0.1".parse().unwrap()), + Some(1) + ); + assert_eq!( + nic_priority(f_prefer_ipv6, "eth0", &"192.168.0.1".parse().unwrap()), + Some(1) + ); + assert_eq!( + nic_priority(f_prefer_ipv6, "eth0", &"fd00::1".parse().unwrap()), + Some(0) + ); + + let f_prefer_addr = &[ + NicFilter::parse("* fd00::1").unwrap(), + NicFilter::parse("eth0 192.168.0.1 * *").unwrap(), + NicFilter::parse("* 192.168.0.2 * *").unwrap(), + NicFilter::parse("eth2").unwrap(), + ]; + assert_eq!( + nic_priority(f_prefer_addr, "eth0", &"192.168.0.2".parse().unwrap()), + Some(2) + ); + assert_eq!( + nic_priority(f_prefer_addr, "eth0", &"192.168.0.1".parse().unwrap()), + Some(1) + ); + assert_eq!( + nic_priority(f_prefer_addr, "eth0", &"fd00::1".parse().unwrap()), + Some(0) + ); + assert_eq!( + nic_priority(f_prefer_addr, "eth1", &"192.168.0.1".parse().unwrap()), + None + ); + assert_eq!( + nic_priority(f_prefer_addr, "eth2", &"fd00::123".parse().unwrap()), + Some(3) + ); + + let f_invert = &[ + NicFilter::parse("! eth1 * 4").unwrap(), + NicFilter::parse("! eth2 * 6").unwrap(), + NicFilter::parse("! lo").unwrap(), + NicFilter::parse("eth1").unwrap(), + NicFilter::parse("*").unwrap(), + ]; + assert_eq!( + nic_priority(f_invert, "eth0", &"192.168.0.2".parse().unwrap()), + Some(4) + ); + assert_eq!( + nic_priority(f_invert, "eth1", &"192.168.0.2".parse().unwrap()), + None + ); + assert_eq!( + nic_priority(f_invert, "eth1", &"fd00::1".parse().unwrap()), + Some(3) + ); + assert_eq!( + nic_priority(f_invert, "eth2", &"192.168.0.2".parse().unwrap()), + Some(4) + ); + assert_eq!( + nic_priority(f_invert, "eth2", &"fd00::1".parse().unwrap()), + None + ); + assert_eq!( + nic_priority(f_invert, "lo", &"fd00::1".parse().unwrap()), + None + ); + } + + #[test] + fn sort_nics() { + let mut nics = [ + Nic { + address: IpAddr::from_str("127.0.0.1").unwrap(), + nic_type: NicType::Ethernet, + name: "a".into(), + priority: 0, + }, + Nic { + address: IpAddr::from_str("192.168.0.2").unwrap(), + nic_type: NicType::Ethernet, + name: "b".into(), + priority: 1, + }, + Nic { + address: IpAddr::from_str("192.168.0.1").unwrap(), + nic_type: NicType::Ethernet, + name: "a".into(), + priority: 0, + }, + Nic { + address: IpAddr::from_str("192.168.0.3").unwrap(), + nic_type: NicType::Rdma, + name: "a".into(), + priority: 0, + }, + Nic { + address: IpAddr::from_str("::1").unwrap(), + nic_type: NicType::Ethernet, + name: "a".into(), + priority: 0, + }, + ]; + + nics.sort(); + + assert_eq!(nics[0].address, IpAddr::from_str("192.168.0.3").unwrap()); + assert_eq!(nics[1].address, IpAddr::from_str("192.168.0.1").unwrap()); + assert_eq!(nics[2].address, IpAddr::from_str("127.0.0.1").unwrap()); + assert_eq!(nics[3].address, IpAddr::from_str("::1").unwrap()); + assert_eq!(nics[4].address, IpAddr::from_str("192.168.0.2").unwrap()); + } +} diff --git a/shared/src/types.rs b/shared/src/types.rs index 2dfdb9e..5d8ed1b 100644 --- a/shared/src/types.rs +++ b/shared/src/types.rs @@ -124,11 +124,23 @@ impl From for pb::NodeType { } /// The network interface type as used by BeeMsg -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum NicType { + Rdma, #[default] Ethernet, - Rdma, +} + +impl FromStr for NicType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + match s { + "tcp" => Ok(Self::Ethernet), + "rdma" => Ok(Self::Rdma), + s => Err(anyhow!("{s} is not a valid nic type")), + } + } } impl_enum_bee_msg_traits!(NicType, Ethernet => 0, Rdma => 2);