use std::{ collections::HashMap, net::IpAddr, sync::{ Arc, Mutex, atomic::{AtomicBool, Ordering}, }, time::{SystemTime, UNIX_EPOCH}, }; use arc_swap::ArcSwap; use cidr::{Ipv4Cidr, Ipv4Inet, Ipv6Cidr, Ipv6Inet}; use dashmap::DashMap; use easytier_proto::{ acl::Acl, common::{FlagsInConfig, PeerFeatureFlag, SecureModeConfig, StunInfo, TunnelInfo}, peer_rpc::{PeerGroupInfo, TrustedCredentialPubkeyProof}, }; use hmac::{Hmac, Mac}; use sha2::Sha256; pub use crate::config::{NetworkIdentity, NetworkSecretDigest}; use crate::{ config::peers::{HostRoutingPolicy, PeerGroupIdentity, PeerRuntimeConfig, PeerRuntimeSnapshot}, config::runtime::CoreRuntimeConfigStore, config::{ CoreConfig, IpPrefix, NodeConfig, PeerId, PeerPolicyConfig, ProxyNetworkConfig, RouteConfig, TrafficConfig, }, events::{CoreEvent, CoreEventSink}, foundation::stats::{LabelSet, LabelType, MetricName, StatsManager}, foundation::token_bucket::{ArcByteLimiter, BucketConfig, TokenBucketManager}, peers::{ credential_manager::{CredentialManager, CredentialStorage}, util::shrink_dashmap, whitelist::check_network_in_relay_whitelist, }, }; pub(crate) const SECRET_PROOF_PREFIX: &[u8] = b"easytier secret proof"; const PEER_EVENT_CAPACITY: usize = 100; #[derive(Debug, Clone)] #[allow(clippy::enum_variant_names)] pub(crate) enum PeerEvent { PeerAdded(PeerId), PeerRemoved(PeerId), PeerConnAdded(easytier_proto::core_peer::peer::PeerConnInfo), PeerConnRemoved(easytier_proto::core_peer::peer::PeerConnInfo), } #[derive(Debug, Clone, PartialEq, Eq)] #[allow(clippy::enum_variant_names)] pub(crate) enum PeerContextEvent { PeerAdded(PeerId), PeerRemoved(PeerId), PeerConnAdded, PeerConnRemoved, } pub(crate) type PeerContextEventSubscriber = tokio::sync::broadcast::Receiver; /// Normalized product and host inputs used to derive one peer runtime version. /// /// The host owns platform-specific normalization of node, route, identity, and /// capability values. Peer policy remains derived in core from the submitted /// flags and ACL. #[derive(Debug, Clone)] pub struct PeerRuntimeSnapshotInput { pub node: NodeConfig, pub routes: RouteConfig, pub network_identity: NetworkIdentity, pub stun_info: StunInfo, pub flags: FlagsInConfig, pub secure_mode: Option, pub host_routing: HostRoutingPolicy, pub acl: Option, pub easytier_version: String, pub vpn_portal_cidr: Option, pub pinned_peers: Vec<(url::Url, Option)>, pub ospf_update_my_foreign_network_interval_sec: u64, pub max_direct_conns_per_peer_in_foreign_network: usize, pub hmac_secret_digest: bool, } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] struct PeerTrafficLimits { instance_recv_bps: Option, foreign_relay_bps: Option, } impl PeerTrafficLimits { fn from_portable(runtime: &PeerRuntimeConfig, flags: &FlagsInConfig) -> Self { let traffic = &runtime.core.traffic; Self { instance_recv_bps: Self::normalize( traffic.instance_recv_bps_limit, flags.instance_recv_bps_limit, ), foreign_relay_bps: Self::normalize( traffic.foreign_relay_bps_limit, flags.foreign_relay_bps_limit, ), } } fn normalize(explicit: Option, legacy: u64) -> Option { match explicit { Some(u64::MAX) => None, Some(limit) => Some(limit), None if !matches!(legacy, 0 | u64::MAX) => Some(legacy), None => None, } } } impl PeerRuntimeSnapshot { pub fn from_host_input(input: PeerRuntimeSnapshotInput) -> Self { let PeerRuntimeSnapshotInput { node, routes, network_identity, stun_info, flags, secure_mode, host_routing, acl, easytier_version, vpn_portal_cidr, pinned_peers, ospf_update_my_foreign_network_interval_sec, max_direct_conns_per_peer_in_foreign_network, hmac_secret_digest, } = input; let feature_flags = PeerFeatureFlag { kcp_input: !flags.disable_kcp_input, no_relay_kcp: flags.disable_relay_kcp, support_conn_list_sync: true, quic_input: !flags.disable_quic_input, no_relay_quic: flags.disable_relay_quic, need_p2p: flags.need_p2p, disable_p2p: flags.disable_p2p, avoid_relay_data: flags.disable_relay_data, ..Default::default() }; let peer_policy = PeerPolicyConfig { p2p_enabled: !flags.disable_p2p, relay_peer_rpc: flags.relay_all_peer_rpc, relay_data: !flags.disable_relay_data, latency_first: flags.latency_first, encryption_required: flags.enable_encryption, }; let traffic = TrafficConfig { mtu: u16::try_from(flags.mtu) .ok() .filter(|configured| *configured != 0), instance_recv_bps_limit: (flags.instance_recv_bps_limit != u64::MAX) .then_some(flags.instance_recv_bps_limit), foreign_relay_bps_limit: (flags.foreign_relay_bps_limit != u64::MAX) .then_some(flags.foreign_relay_bps_limit), }; let avoid_relay_data_preference = check_network_in_relay_whitelist( &flags.relay_network_whitelist, &network_identity.network_name, ) .is_err(); let (acl_group_declarations, peer_group_memberships) = peer_acl_groups(acl.as_ref()); Self { runtime: PeerRuntimeConfig { core: CoreConfig { node, routes, peer_policy, traffic, }, network_identity, stun_info, feature_flags, secure_mode, host_routing, }, easytier_version, avoid_relay_data_preference, flags, vpn_portal_cidr, pinned_peers, peer_group_memberships, acl_group_declarations, ospf_update_my_foreign_network_interval_sec, max_direct_conns_per_peer_in_foreign_network, hmac_secret_digest, } } fn traffic_limits(&self) -> PeerTrafficLimits { PeerTrafficLimits::from_portable(&self.runtime, &self.flags) } } fn peer_acl_groups(acl: Option<&Acl>) -> (Vec, Vec) { let group = acl .and_then(|acl| acl.acl_v1.as_ref()) .and_then(|acl| acl.group.as_ref()); let declarations = group.map_or_else(Vec::new, |group| { group .declares .iter() .map(|identity| PeerGroupIdentity { group_name: identity.group_name.clone(), group_secret: identity.group_secret.clone(), }) .collect() }); let memberships = group.map_or_else(Vec::new, |group| { group .declares .iter() .filter(|identity| group.members.contains(&identity.group_name)) .map(|identity| PeerGroupIdentity { group_name: identity.group_name.clone(), group_secret: identity.group_secret.clone(), }) .collect() }); (declarations, memberships) } /// Supplies the instance's current STUN observation. pub(crate) trait PeerStunInfoSource: Send + Sync { fn stun_info(&self) -> StunInfo { StunInfo::default() } } impl PeerStunInfoSource for () {} /// Supplies public-IPv6 state observed or leased by the host. pub(crate) trait PeerPublicIpv6State: Send + Sync { fn public_ipv6_lease_contains(&self, _ip: &std::net::Ipv6Addr) -> bool { false } fn public_ipv6_provider_enabled(&self) -> bool { false } fn advertised_ipv6_public_addr_prefix(&self) -> Option { None } } impl PeerPublicIpv6State for () {} /// Host adapters used to assemble the core-owned peer context. Each field stays /// narrow so peer modules cannot reach unrelated host state after construction. pub(crate) struct CorePeerContextAdapters { pub stun_info_source: Option>, pub events: Arc, pub credential_storage: Option>, } /// Peer context backed by one core-owned submitted snapshot and its instance /// runtime resources. pub(crate) struct CorePeerContext { config: CoreRuntimeConfigStore, avoid_relay_data_preference: AtomicBool, stun_info_source: Option>, public_ipv6_state: Arc, limiter_state: Mutex, stats_manager: Arc, credentials: Arc, trusted_keys: Arc, peer_events: tokio::sync::broadcast::Sender, events: Arc, } impl CorePeerContext { pub(crate) fn new( config: CoreRuntimeConfigStore, public_ipv6_state: Arc, adapters: CorePeerContextAdapters, ) -> Self { Self::new_with_stats_manager( config, public_ipv6_state, adapters, Arc::new(StatsManager::new()), ) } /// Builds a foreign-network context that contributes to the same /// instance-level metrics registry while retaining independent identity, /// credential, trusted-key, event, and limiter state. pub fn new_foreign( config: CoreRuntimeConfigStore, adapters: CorePeerContextAdapters, parent: &CorePeerContext, ) -> Self { Self::new_with_stats_manager(config, Arc::new(()), adapters, parent.stats_manager()) } fn new_with_stats_manager( config: CoreRuntimeConfigStore, public_ipv6_state: Arc, adapters: CorePeerContextAdapters, stats_manager: Arc, ) -> Self { let avoid_relay_data_preference = AtomicBool::new(config.snapshot().peer.avoid_relay_data_preference); let credentials = Arc::new( adapters .credential_storage .map_or_else(CredentialManager::new, CredentialManager::from_storage), ); Self { config, avoid_relay_data_preference, stun_info_source: adapters.stun_info_source, public_ipv6_state, limiter_state: Mutex::new(CoreLimiterState::default()), stats_manager, credentials, trusted_keys: Arc::new(TrustedKeyMapManager::new()), peer_events: tokio::sync::broadcast::channel(PEER_EVENT_CAPACITY).0, events: adapters.events, } } fn snapshot(&self) -> Arc { self.config.snapshot().peer.clone() } pub fn stats_manager(&self) -> Arc { self.stats_manager.clone() } pub fn credential_manager(&self) -> Arc { self.credentials.clone() } fn record_control_metric( &self, network_name: &str, bytes: u64, bytes_metric: MetricName, packets_metric: MetricName, ) { let labels = LabelSet::new().with_label_type(LabelType::NetworkName(network_name.to_owned())); self.stats_manager .get_counter(bytes_metric, labels.clone()) .add(bytes); self.stats_manager.get_counter(packets_metric, labels).inc(); } fn get_or_create_limiter(&self, key: &str, bps: u64) -> Option { let mut state = self.limiter_state.lock().unwrap(); if state.stopped { return None; } let manager = state.manager.get_or_insert_with(TokenBucketManager::new); Some(manager.get_or_create(key, BucketConfig::with_default_capacity(bps))) } pub(crate) async fn stop(&self) { let manager = { let mut state = self.limiter_state.lock().unwrap(); state.stopped = true; state.manager.take() }; if let Some(manager) = manager { manager.stop().await; } } } #[derive(Default)] struct CoreLimiterState { manager: Option, stopped: bool, } fn config_ipv4(value: &IpPrefix) -> Option { let IpAddr::V4(address) = value.address else { return None; }; Ipv4Inet::new(address, value.prefix_len).ok() } fn config_ipv4_cidr(value: &IpPrefix) -> Option { let IpAddr::V4(address) = value.address else { return None; }; Ipv4Cidr::new(address, value.prefix_len).ok() } fn config_ipv6(value: &IpPrefix) -> Option { let IpAddr::V6(address) = value.address else { return None; }; Ipv6Inet::new(address, value.prefix_len).ok() } /// Source of a trusted public key propagated by the OSPF route layer. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TrustedKeySource { OspfNode, OspfCredential, } #[derive(Debug, Clone)] pub(crate) struct TrustedKeyMetadata { pub source: TrustedKeySource, pub expiry_unix: Option, } impl TrustedKeyMetadata { pub fn is_expired(&self) -> bool { if let Some(expiry) = self.expiry_unix { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs() as i64; return now >= expiry; } false } } pub(crate) type TrustedKeyMap = HashMap, TrustedKeyMetadata>; pub(crate) struct TrustedKeyMapManager { network_trusted_keys: DashMap>, } impl TrustedKeyMapManager { pub fn new() -> Self { Self { network_trusted_keys: DashMap::new(), } } pub fn update_trusted_keys(&self, network_name: &str, trusted_keys: TrustedKeyMap) { match self.network_trusted_keys.entry(network_name.to_string()) { dashmap::Entry::Vacant(entry) => { entry.insert(ArcSwap::new(Arc::new(trusted_keys))); } dashmap::Entry::Occupied(entry) => { entry.get().store(Arc::new(trusted_keys)); } } } pub fn remove_trusted_keys(&self, network_name: &str) { self.network_trusted_keys.remove(network_name); shrink_dashmap(&self.network_trusted_keys, None); } pub fn verify_trusted_key(&self, pubkey: &[u8], network_name: &str) -> bool { self.verify_trusted_key_with_source(pubkey, network_name, None) } pub fn verify_trusted_key_with_source( &self, pubkey: &[u8], network_name: &str, source: Option, ) -> bool { let Some(trusted_keys) = self .network_trusted_keys .get(network_name) .map(|v| v.load_full()) else { return false; }; let Some(metadata) = trusted_keys.get(&pubkey.to_vec()) else { return false; }; if let Some(source) = source { metadata.source == source && !metadata.is_expired() } else { !metadata.is_expired() } } pub fn list_trusted_keys(&self, network_name: &str) -> Vec<(Vec, TrustedKeyMetadata)> { let Some(trusted_keys) = self .network_trusted_keys .get(network_name) .map(|v| v.load_full()) else { return Vec::new(); }; let mut items = trusted_keys .iter() .filter(|(_, metadata)| !metadata.is_expired()) .map(|(pubkey, metadata)| (pubkey.clone(), metadata.clone())) .collect::>(); items.sort_by(|left, right| left.0.cmp(&right.0)); items } } impl Default for TrustedKeyMapManager { fn default() -> Self { Self::new() } } /// Runtime dependency interface for the peers module. /// /// `PeerContext` is intentionally scoped to `easytier-core::peers`; other core /// modules should depend on their own narrow DTOs or traits instead of treating /// this as a core-wide global context. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub(crate) struct PeerPacketPolicy { pub(crate) disable_relay_data: bool, pub(crate) p2p_only: bool, pub(crate) latency_first: bool, pub(crate) disable_p2p: bool, pub(crate) lazy_p2p: bool, } impl PeerPacketPolicy { fn from_flags(flags: &FlagsInConfig) -> Self { Self { disable_relay_data: flags.disable_relay_data, p2p_only: flags.p2p_only, latency_first: flags.latency_first && !flags.p2p_only, disable_p2p: flags.disable_p2p, lazy_p2p: flags.lazy_p2p, } } } pub(crate) trait PeerContext: Send + Sync { fn host_routing_policy(&self) -> HostRoutingPolicy { HostRoutingPolicy::default() } fn network_identity(&self) -> NetworkIdentity; fn network_name(&self) -> String { self.network_identity().network_name } fn flags(&self) -> FlagsInConfig { FlagsInConfig::default() } fn packet_policy(&self) -> PeerPacketPolicy { PeerPacketPolicy::from_flags(&self.flags()) } fn disable_relay_data(&self) -> bool { self.packet_policy().disable_relay_data } fn secure_mode(&self) -> Option { None } fn stun_info(&self) -> StunInfo { StunInfo::default() } fn instance_id(&self) -> uuid::Uuid { uuid::Uuid::nil() } fn ipv4(&self) -> Option { None } fn ipv6(&self) -> Option { None } fn is_ip_local_ipv6(&self, ip: &std::net::Ipv6Addr) -> bool { self.ipv6() .map(|addr| addr.address() == *ip) .unwrap_or(false) } fn is_ip_local_virtual_ip(&self, ip: &IpAddr) -> bool { match ip { IpAddr::V4(v4) => self .ipv4() .map(|addr| addr.address() == *v4) .unwrap_or(false), IpAddr::V6(v6) => self.is_ip_local_ipv6(v6), } } fn proxy_cidrs(&self) -> Vec { Vec::new() } fn proxy_networks(&self) -> Vec { Vec::new() } fn vpn_portal_cidr(&self) -> Option { None } fn hostname(&self) -> String { String::new() } fn feature_flags(&self) -> PeerFeatureFlag { PeerFeatureFlag::default() } fn set_avoid_relay_data_preference(&self, _avoid_relay_data: bool) -> bool { false } fn subscribe_runtime_changes(&self) -> Option> { None } fn easytier_version(&self) -> String { env!("CARGO_PKG_VERSION").to_string() } fn ospf_update_my_foreign_network_interval_sec(&self) -> u64 { 10 } fn max_direct_conns_per_peer_in_foreign_network(&self) -> usize { 3 } fn hmac_secret_digest(&self) -> bool { false } fn advertised_ipv6_public_addr_prefix(&self) -> Option { None } fn is_ip_in_same_network(&self, _ip: &IpAddr) -> bool { false } fn peer_groups(&self, _peer_id: PeerId) -> Vec { Vec::new() } fn acl_group_declarations(&self) -> Vec { Vec::new() } fn pinned_remote_static_pubkey(&self, _tunnel_info: Option<&TunnelInfo>) -> Option { None } fn secret_proof(&self, _challenge: &[u8]) -> Option> { None } fn secret_digest(&self, network_identity: &NetworkIdentity) -> Vec { network_identity .secret_digest() .unwrap_or_default() .to_vec() } fn is_pubkey_trusted(&self, _pubkey: &[u8], _network_name: &str) -> bool { false } fn is_pubkey_trusted_with_source( &self, _pubkey: &[u8], _network_name: &str, _source: TrustedKeySource, ) -> bool { false } fn list_trusted_keys(&self, _network_name: &str) -> Vec<(Vec, TrustedKeyMetadata)> { Vec::new() } fn trusted_credential_pubkeys( &self, _network_secret: &str, ) -> Vec { Vec::new() } fn remove_expired_credentials(&self) -> bool { false } fn issue_credential_changed(&self) {} fn update_trusted_keys(&self, _keys: TrustedKeyMap, _network_name: &str) {} fn remove_trusted_keys(&self, _network_name: &str) {} fn record_control_tx(&self, _network_name: &str, _bytes: u64) {} fn record_control_rx(&self, _network_name: &str, _bytes: u64) {} fn recv_limiter( &self, _network_name: &str, _is_foreign_network: bool, ) -> Option { None } fn foreign_forward_limiter(&self, _network_name: &str) -> Option { None } fn issue_event(&self, _event: PeerEvent) {} fn subscribe_peer_events(&self) -> Option { None } } pub(crate) type ArcPeerContext = Arc; pub(crate) fn secret_proof_from_secret(secret: &str, challenge: &[u8]) -> Option> { let mut mac = Hmac::::new_from_slice(secret.as_bytes()).ok()?; mac.update(SECRET_PROOF_PREFIX); mac.update(challenge); Some(mac) } impl PeerContext for CorePeerContext { fn max_direct_conns_per_peer_in_foreign_network(&self) -> usize { self.snapshot().max_direct_conns_per_peer_in_foreign_network } fn network_identity(&self) -> NetworkIdentity { self.snapshot().runtime.network_identity.clone() } fn flags(&self) -> FlagsInConfig { self.snapshot().flags.clone() } fn packet_policy(&self) -> PeerPacketPolicy { self.config .with_snapshot(|snapshot| PeerPacketPolicy::from_flags(&snapshot.peer.flags)) } fn host_routing_policy(&self) -> HostRoutingPolicy { self.snapshot().runtime.host_routing } fn secure_mode(&self) -> Option { self.snapshot().runtime.secure_mode.clone() } fn stun_info(&self) -> StunInfo { self.stun_info_source .as_ref() .map(|source| source.stun_info()) .unwrap_or_else(|| self.snapshot().runtime.stun_info.clone()) } fn instance_id(&self) -> uuid::Uuid { self.snapshot() .runtime .core .node .instance_id .map(uuid::Uuid::from_bytes) .unwrap_or_else(uuid::Uuid::nil) } fn ipv4(&self) -> Option { self.config.with_snapshot(|snapshot| { snapshot .peer .runtime .core .routes .ipv4 .as_ref() .and_then(config_ipv4) }) } fn ipv6(&self) -> Option { self.snapshot() .runtime .core .routes .ipv6 .as_ref() .and_then(config_ipv6) } fn is_ip_local_ipv6(&self, ip: &std::net::Ipv6Addr) -> bool { self.ipv6().is_some_and(|address| address.address() == *ip) || self.public_ipv6_state.public_ipv6_lease_contains(ip) } fn proxy_cidrs(&self) -> Vec { self.snapshot() .runtime .core .routes .proxy_networks .iter() .filter_map(|proxy| config_ipv4_cidr(proxy.mapped.as_ref().unwrap_or(&proxy.real))) .collect() } fn proxy_networks(&self) -> Vec { self.snapshot().runtime.core.routes.proxy_networks.clone() } fn vpn_portal_cidr(&self) -> Option { self.snapshot().vpn_portal_cidr } fn hostname(&self) -> String { self.snapshot() .runtime .core .node .hostname .clone() .unwrap_or_default() } fn feature_flags(&self) -> PeerFeatureFlag { let snapshot = self.snapshot(); let mut flags = snapshot.runtime.feature_flags; flags.avoid_relay_data = snapshot.flags.disable_relay_data || self.avoid_relay_data_preference.load(Ordering::Acquire); flags.ipv6_public_addr_provider |= self.public_ipv6_state.public_ipv6_provider_enabled(); flags } fn set_avoid_relay_data_preference(&self, avoid_relay_data: bool) -> bool { let before = self.feature_flags().avoid_relay_data; self.avoid_relay_data_preference .store(avoid_relay_data, Ordering::Release); before != self.feature_flags().avoid_relay_data } fn subscribe_runtime_changes(&self) -> Option> { Some(self.config.subscribe_peer_runtime_changes()) } fn easytier_version(&self) -> String { self.snapshot().easytier_version.clone() } fn ospf_update_my_foreign_network_interval_sec(&self) -> u64 { self.snapshot().ospf_update_my_foreign_network_interval_sec } fn hmac_secret_digest(&self) -> bool { self.snapshot().hmac_secret_digest } fn advertised_ipv6_public_addr_prefix(&self) -> Option { self.public_ipv6_state.advertised_ipv6_public_addr_prefix() } fn is_ip_in_same_network(&self, ip: &IpAddr) -> bool { match ip { IpAddr::V4(ip) => self.ipv4().is_some_and(|network| network.contains(ip)), IpAddr::V6(ip) => self.ipv6().is_some_and(|network| network.contains(ip)), } } fn pinned_remote_static_pubkey(&self, tunnel_info: Option<&TunnelInfo>) -> Option { let remote_url = tunnel_info .and_then(|info| info.remote_addr.as_ref())? .url .parse::() .ok()?; self.snapshot() .pinned_peers .iter() .find(|(uri, _)| *uri == remote_url) .and_then(|(_, public_key)| public_key.clone()) } fn secret_proof(&self, challenge: &[u8]) -> Option> { let snapshot = self.snapshot(); let secret = snapshot.runtime.network_identity.network_secret.as_ref()?; secret_proof_from_secret(secret, challenge) } fn secret_digest(&self, network_identity: &NetworkIdentity) -> Vec { let snapshot = self.snapshot(); if snapshot.hmac_secret_digest { snapshot .runtime .network_identity .network_secret .as_deref() .and_then(|secret| secret_proof_from_secret(secret, b"digest")) .map(|mac| mac.finalize().into_bytes().to_vec()) .unwrap_or_default() } else { network_identity .secret_digest() .unwrap_or_default() .to_vec() } } fn peer_groups(&self, peer_id: PeerId) -> Vec { self.snapshot() .peer_group_memberships .iter() .map(|group| { PeerGroupInfo::generate_with_proof( group.group_name.clone(), group.group_secret.clone(), peer_id, ) }) .collect() } fn acl_group_declarations(&self) -> Vec { self.snapshot().acl_group_declarations.clone() } fn is_pubkey_trusted(&self, pubkey: &[u8], network_name: &str) -> bool { if self.trusted_keys.verify_trusted_key(pubkey, network_name) { return true; } network_name == self.snapshot().runtime.network_identity.network_name && self.credentials.is_pubkey_trusted(pubkey) } fn is_pubkey_trusted_with_source( &self, pubkey: &[u8], network_name: &str, source: TrustedKeySource, ) -> bool { self.trusted_keys .verify_trusted_key_with_source(pubkey, network_name, Some(source)) } fn list_trusted_keys(&self, network_name: &str) -> Vec<(Vec, TrustedKeyMetadata)> { self.trusted_keys.list_trusted_keys(network_name) } fn trusted_credential_pubkeys( &self, network_secret: &str, ) -> Vec { self.credentials.get_trusted_pubkeys(network_secret) } fn remove_expired_credentials(&self) -> bool { self.credentials.remove_expired_credentials() } fn issue_credential_changed(&self) { self.events.emit(CoreEvent::CredentialChanged); } fn update_trusted_keys(&self, keys: TrustedKeyMap, network_name: &str) { self.trusted_keys.update_trusted_keys(network_name, keys); } fn remove_trusted_keys(&self, network_name: &str) { self.trusted_keys.remove_trusted_keys(network_name); } fn record_control_tx(&self, network_name: &str, bytes: u64) { self.record_control_metric( network_name, bytes, MetricName::TrafficControlBytesTx, MetricName::TrafficControlPacketsTx, ); } fn record_control_rx(&self, network_name: &str, bytes: u64) { self.record_control_metric( network_name, bytes, MetricName::TrafficControlBytesRx, MetricName::TrafficControlPacketsRx, ); } fn recv_limiter(&self, network_name: &str, is_foreign_network: bool) -> Option { let limits = self.snapshot().traffic_limits(); let (key, bps) = if is_foreign_network && let Some(limit) = limits.foreign_relay_bps { (format!("peer:foreign:{network_name}:recv"), limit) } else { ("peer:instance:recv".to_owned(), limits.instance_recv_bps?) }; self.get_or_create_limiter(&key, bps) } fn foreign_forward_limiter(&self, network_name: &str) -> Option { let bps = self.snapshot().traffic_limits().foreign_relay_bps?; self.get_or_create_limiter(&format!("peer:foreign:{network_name}:forward"), bps) } fn issue_event(&self, event: PeerEvent) { let context_event = match &event { PeerEvent::PeerAdded(peer_id) => PeerContextEvent::PeerAdded(*peer_id), PeerEvent::PeerRemoved(peer_id) => PeerContextEvent::PeerRemoved(*peer_id), PeerEvent::PeerConnAdded(_) => PeerContextEvent::PeerConnAdded, PeerEvent::PeerConnRemoved(_) => PeerContextEvent::PeerConnRemoved, }; let _ = self.peer_events.send(context_event); let event = match event { PeerEvent::PeerAdded(peer_id) => CoreEvent::PeerAdded(peer_id), PeerEvent::PeerRemoved(peer_id) => CoreEvent::PeerRemoved(peer_id), PeerEvent::PeerConnAdded(info) => CoreEvent::PeerConnAdded(info), PeerEvent::PeerConnRemoved(info) => CoreEvent::PeerConnRemoved(info), }; self.events.emit(event); } fn subscribe_peer_events(&self) -> Option { Some(self.peer_events.subscribe()) } } #[cfg(test)] pub(crate) mod tests { #![allow(clippy::field_reassign_with_default)] use super::*; use crate::config::runtime::{CoreRuntimeConfig, CoreRuntimeConfigStore}; use crate::peers::test_support::{NoopPeerContext, PeerContextTestExt}; use std::sync::atomic::{AtomicBool, Ordering}; impl PeerRuntimeSnapshot { pub(crate) fn set_acl_groups(&mut self, acl: Option<&Acl>) { (self.acl_group_declarations, self.peer_group_memberships) = peer_acl_groups(acl); } } impl CorePeerContext { pub(crate) fn trusted_key_manager(&self) -> Arc { self.trusted_keys.clone() } } impl PeerContextTestExt for CorePeerContext { fn runtime_config(&self) -> PeerRuntimeConfig { let mut runtime = self.snapshot().runtime.clone(); runtime.stun_info = self.stun_info(); runtime } } #[derive(Default)] struct TestPeerEventSink { events: Mutex>, credential_changed: AtomicBool, } impl CoreEventSink for TestPeerEventSink { fn emit(&self, event: CoreEvent) { if matches!(&event, CoreEvent::CredentialChanged) { self.credential_changed.store(true, Ordering::Release); } self.events.lock().unwrap().push(event); } } fn test_core_context_adapters(events: Arc) -> CorePeerContextAdapters { CorePeerContextAdapters { stun_info_source: Some(Arc::new(())), events, credential_storage: None, } } fn submitted_snapshot(hostname: &str, disable_relay_data: bool) -> PeerRuntimeSnapshot { let mut flags = FlagsInConfig::default(); flags.disable_relay_data = disable_relay_data; PeerRuntimeSnapshot::new( PeerRuntimeConfig { core: CoreConfig { node: NodeConfig { hostname: Some(hostname.to_owned()), ..Default::default() }, ..Default::default() }, network_identity: NetworkIdentity::default(), stun_info: StunInfo::default(), feature_flags: PeerFeatureFlag::default(), secure_mode: None, host_routing: HostRoutingPolicy::default(), }, flags, ) } fn host_snapshot_input(flags: FlagsInConfig, acl: Option) -> PeerRuntimeSnapshotInput { PeerRuntimeSnapshotInput { node: NodeConfig { peer_id: None, instance_id: Some([7; 16]), hostname: Some("host-node".to_owned()), network_name: "host-network".to_owned(), }, routes: RouteConfig { ipv4: Some(IpPrefix::new("10.20.0.7".parse().unwrap(), 16).unwrap()), ..Default::default() }, network_identity: NetworkIdentity::new("host-network".to_owned(), "secret".to_owned()), stun_info: StunInfo::default(), flags, secure_mode: None, host_routing: HostRoutingPolicy { local_exit_node_fallback: true, }, acl, easytier_version: "host-version".to_owned(), vpn_portal_cidr: Some("10.30.0.0/24".parse().unwrap()), pinned_peers: vec![( "tcp://192.0.2.10:11010".parse().unwrap(), Some("peer-key".to_owned()), )], ospf_update_my_foreign_network_interval_sec: 17, max_direct_conns_per_peer_in_foreign_network: 5, hmac_secret_digest: true, } } #[test] fn host_input_derives_peer_policy_features_and_traffic() { let mut flags = FlagsInConfig::default(); flags.disable_p2p = true; flags.need_p2p = true; flags.relay_all_peer_rpc = true; flags.disable_relay_data = true; flags.latency_first = true; flags.enable_encryption = false; flags.disable_kcp_input = true; flags.disable_relay_kcp = true; flags.disable_quic_input = true; flags.disable_relay_quic = true; flags.mtu = 1400; flags.instance_recv_bps_limit = 0; flags.foreign_relay_bps_limit = u64::MAX; flags.relay_network_whitelist = "host-network".to_owned(); let snapshot = PeerRuntimeSnapshot::from_host_input(host_snapshot_input(flags.clone(), None)); let runtime = &snapshot.runtime; assert_eq!(snapshot.flags, flags); assert!(!runtime.core.peer_policy.p2p_enabled); assert!(runtime.core.peer_policy.relay_peer_rpc); assert!(!runtime.core.peer_policy.relay_data); assert!(runtime.core.peer_policy.latency_first); assert!(!runtime.core.peer_policy.encryption_required); assert_eq!(runtime.core.traffic.mtu, Some(1400)); assert_eq!(runtime.core.traffic.instance_recv_bps_limit, Some(0)); assert_eq!(runtime.core.traffic.foreign_relay_bps_limit, None); assert!(!runtime.feature_flags.kcp_input); assert!(runtime.feature_flags.no_relay_kcp); assert!(runtime.feature_flags.support_conn_list_sync); assert!(!runtime.feature_flags.quic_input); assert!(runtime.feature_flags.no_relay_quic); assert!(runtime.feature_flags.need_p2p); assert!(runtime.feature_flags.disable_p2p); assert!(runtime.feature_flags.avoid_relay_data); assert!(!snapshot.avoid_relay_data_preference); } #[test] fn host_input_derives_acl_groups_and_preserves_explicit_inputs() { let acl = Acl { acl_v1: Some(easytier_proto::acl::AclV1 { chains: Vec::new(), group: Some(easytier_proto::acl::GroupInfo { declares: vec![ easytier_proto::acl::GroupIdentity { group_name: "ops".to_owned(), group_secret: "ops-secret".to_owned(), }, easytier_proto::acl::GroupIdentity { group_name: "audit".to_owned(), group_secret: "audit-secret".to_owned(), }, ], members: vec!["ops".to_owned(), "undeclared".to_owned()], }), }), }; let mut flags = FlagsInConfig::default(); flags.relay_network_whitelist = "other-network".to_owned(); let snapshot = PeerRuntimeSnapshot::from_host_input(host_snapshot_input(flags, Some(acl))); assert!(snapshot.avoid_relay_data_preference); assert_eq!(snapshot.easytier_version, "host-version"); assert_eq!( snapshot.vpn_portal_cidr, Some("10.30.0.0/24".parse().unwrap()) ); assert_eq!( snapshot.pinned_peers, vec![( "tcp://192.0.2.10:11010".parse().unwrap(), Some("peer-key".to_owned()) )] ); assert_eq!(snapshot.ospf_update_my_foreign_network_interval_sec, 17); assert_eq!(snapshot.max_direct_conns_per_peer_in_foreign_network, 5); assert!(snapshot.hmac_secret_digest); assert!(snapshot.runtime.host_routing.local_exit_node_fallback); assert_eq!( snapshot.acl_group_declarations, vec![ PeerGroupIdentity { group_name: "ops".to_owned(), group_secret: "ops-secret".to_owned(), }, PeerGroupIdentity { group_name: "audit".to_owned(), group_secret: "audit-secret".to_owned(), }, ] ); assert_eq!( snapshot.peer_group_memberships, vec![PeerGroupIdentity { group_name: "ops".to_owned(), group_secret: "ops-secret".to_owned(), }] ); } #[test] fn records_control_traffic_in_core_owned_metrics() { let context = CorePeerContext::new( CoreRuntimeConfigStore::new( CoreRuntimeConfig::default(), Arc::new(PeerRuntimeSnapshot::default()), ), Arc::new(()), test_core_context_adapters(Arc::new(())), ); let labels = LabelSet::new().with_label_type(LabelType::NetworkName("metrics-network".to_owned())); PeerContext::record_control_tx(&context, "metrics-network", 128); assert_eq!( context .stats_manager() .get_metric(MetricName::TrafficControlBytesTx, &labels) .unwrap() .value, 128 ); assert_eq!( context .stats_manager() .get_metric(MetricName::TrafficControlPacketsTx, &labels) .unwrap() .value, 1 ); } #[test] fn explicit_traffic_limits_preserve_zero_and_override_flags() { let mut runtime = PeerRuntimeSnapshot::default().runtime; runtime.core.traffic.instance_recv_bps_limit = Some(0); runtime.core.traffic.foreign_relay_bps_limit = Some(2048); let mut flags = FlagsInConfig::default(); flags.instance_recv_bps_limit = 1024; flags.foreign_relay_bps_limit = 4096; let snapshot = PeerRuntimeSnapshot::new(runtime, flags); assert_eq!(snapshot.traffic_limits().instance_recv_bps, Some(0)); assert_eq!(snapshot.traffic_limits().foreign_relay_bps, Some(2048)); } #[test] fn legacy_traffic_limits_ignore_unlimited_sentinels() { let runtime = PeerRuntimeSnapshot::default().runtime; let mut flags = FlagsInConfig::default(); flags.instance_recv_bps_limit = 1024; flags.foreign_relay_bps_limit = u64::MAX; let snapshot = PeerRuntimeSnapshot::new(runtime, flags); assert_eq!(snapshot.traffic_limits().instance_recv_bps, Some(1024)); assert_eq!(snapshot.traffic_limits().foreign_relay_bps, None); } #[test] fn explicit_unlimited_limits_override_legacy_values() { let mut runtime = PeerRuntimeSnapshot::default().runtime; runtime.core.traffic.instance_recv_bps_limit = Some(u64::MAX); runtime.core.traffic.foreign_relay_bps_limit = Some(u64::MAX); let mut flags = FlagsInConfig::default(); flags.instance_recv_bps_limit = 1024; flags.foreign_relay_bps_limit = 2048; let snapshot = PeerRuntimeSnapshot::new(runtime, flags); assert_eq!(snapshot.traffic_limits(), PeerTrafficLimits::default()); } #[test] fn portable_traffic_limits_default_to_unlimited() { let runtime = PeerRuntimeSnapshot::default().runtime; let snapshot = PeerRuntimeSnapshot::new(runtime, FlagsInConfig::default()); assert_eq!(snapshot.traffic_limits(), PeerTrafficLimits::default()); } fn submitted_config(snapshot: PeerRuntimeSnapshot) -> CoreRuntimeConfigStore { CoreRuntimeConfigStore::new(CoreRuntimeConfig::default(), Arc::new(snapshot)) } #[test] fn core_peer_context_separates_config_versions_from_live_support() { let config = submitted_config(submitted_snapshot("before", false)); let context = CorePeerContext::new( config.clone(), Arc::new(()), test_core_context_adapters(Arc::new(())), ); assert_eq!(context.hostname(), "before"); assert!(!context.feature_flags().avoid_relay_data); context.set_avoid_relay_data_preference(true); assert!(context.feature_flags().avoid_relay_data); config.update_peer(Arc::new(submitted_snapshot("after", true))); context.set_avoid_relay_data_preference(false); assert_eq!(context.hostname(), "after"); assert!(context.feature_flags().avoid_relay_data); config.update_peer(Arc::new(submitted_snapshot("after", false))); assert!(!context.feature_flags().avoid_relay_data); } #[tokio::test] async fn core_peer_context_owns_events_and_projects_them_to_sink() { let config = submitted_config(submitted_snapshot("events", false)); let sink = Arc::new(TestPeerEventSink::default()); let context = CorePeerContext::new( config, Arc::new(()), test_core_context_adapters(sink.clone()), ); let mut events = context.subscribe_peer_events().unwrap(); context.issue_event(PeerEvent::PeerAdded(7)); assert_eq!(events.recv().await.unwrap(), PeerContextEvent::PeerAdded(7)); assert!(matches!( sink.events.lock().unwrap().as_slice(), [CoreEvent::PeerAdded(7)] )); } #[test] fn core_peer_context_owns_trusted_keys_and_projects_credential_changes() { let config = submitted_config(submitted_snapshot("trust", false)); let events = Arc::new(TestPeerEventSink::default()); let context = CorePeerContext::new( config, Arc::new(()), test_core_context_adapters(events.clone()), ); let public_key = vec![7; 32]; let mut keys = TrustedKeyMap::new(); keys.insert( public_key.clone(), TrustedKeyMetadata { source: TrustedKeySource::OspfNode, expiry_unix: None, }, ); context.update_trusted_keys(keys, "foreign"); context.issue_credential_changed(); assert!(context.is_pubkey_trusted(&public_key, "foreign")); assert_eq!(context.list_trusted_keys("foreign").len(), 1); assert!(events.credential_changed.load(Ordering::Acquire)); } #[tokio::test] async fn foreign_forward_limiter_is_independent_from_peer_receive_limiter() { let mut snapshot = submitted_snapshot("limiter", false); snapshot.runtime.core.traffic.foreign_relay_bps_limit = Some(1024); let config = submitted_config(snapshot); let context = CorePeerContext::new( config, Arc::new(()), test_core_context_adapters(Arc::new(())), ); let receive = context.recv_limiter("foreign", true).unwrap(); let receive_again = context.recv_limiter("foreign", true).unwrap(); let forward = context.foreign_forward_limiter("foreign").unwrap(); assert!(Arc::ptr_eq(&receive, &receive_again)); assert!(!Arc::ptr_eq(&receive, &forward)); context.stop().await; } #[tokio::test] async fn foreign_forward_limiter_does_not_fall_back_to_instance_limit() { let mut snapshot = submitted_snapshot("limiter", false); snapshot.runtime.core.traffic.foreign_relay_bps_limit = None; snapshot.runtime.core.traffic.instance_recv_bps_limit = Some(1024); let config = submitted_config(snapshot); let context = CorePeerContext::new( config, Arc::new(()), test_core_context_adapters(Arc::new(())), ); assert!(context.recv_limiter("foreign", true).is_some()); assert!(context.foreign_forward_limiter("foreign").is_none()); context.stop().await; } #[test] fn noop_peer_context_uses_runtime_secret_proof_prefix() { let context = NoopPeerContext::new(NetworkIdentity { network_name: "net".to_string(), network_secret: Some("secret".to_string()), network_secret_digest: None, }); let proof = context .secret_proof(b"challenge") .unwrap() .finalize() .into_bytes() .to_vec(); let expected = secret_proof_from_secret("secret", b"challenge") .unwrap() .finalize() .into_bytes() .to_vec(); assert_eq!(proof, expected); } struct RuntimeConfigContext { instance_id: uuid::Uuid, } impl PeerContext for RuntimeConfigContext { fn network_identity(&self) -> NetworkIdentity { NetworkIdentity { network_name: "net".to_string(), network_secret: Some("secret".to_string()), network_secret_digest: None, } } fn instance_id(&self) -> uuid::Uuid { self.instance_id } fn ipv4(&self) -> Option { Some("10.1.0.1/24".parse().unwrap()) } fn ipv6(&self) -> Option { Some("2001:db8::1/64".parse().unwrap()) } fn hostname(&self) -> String { "node-a".to_string() } } impl PeerContextTestExt for RuntimeConfigContext {} #[test] fn runtime_config_preserves_peer_context_snapshot() { let instance_id = uuid::Uuid::from_u128(0x11223344556677889900aabbccddeeff); let context = RuntimeConfigContext { instance_id }; let config = context.runtime_config(); assert_eq!(config.network_identity.network_name, "net"); assert_eq!(config.core.node.instance_id, Some(*instance_id.as_bytes())); assert_eq!(config.core.node.hostname.as_deref(), Some("node-a")); assert_eq!(config.core.node.network_name, "net"); assert_eq!( config.core.routes.ipv4, Some(IpPrefix::new("10.1.0.1".parse().unwrap(), 24).unwrap()) ); assert_eq!( config.core.routes.ipv6, Some(IpPrefix::new("2001:db8::1".parse().unwrap(), 64).unwrap()) ); } #[test] fn core_owned_peer_context_reads_normalized_snapshot() { let instance_id = uuid::Uuid::from_u128(0x00112233445566778899aabbccddeeff); let runtime = PeerRuntimeConfig { core: CoreConfig { node: NodeConfig { peer_id: Some(7), instance_id: Some(*instance_id.as_bytes()), hostname: Some("config-node".to_owned()), network_name: "config-net".to_owned(), }, routes: RouteConfig { ipv4: Some(IpPrefix::new("10.20.0.7".parse().unwrap(), 16).unwrap()), ipv6: Some(IpPrefix::new("2001:db8::7".parse().unwrap(), 64).unwrap()), proxy_networks: vec![ crate::config::ProxyNetworkConfig { real: IpPrefix::new("10.40.0.0".parse().unwrap(), 16).unwrap(), mapped: Some(IpPrefix::new("10.50.0.0".parse().unwrap(), 16).unwrap()), }, crate::config::ProxyNetworkConfig { real: IpPrefix::new("10.60.0.0".parse().unwrap(), 16).unwrap(), mapped: None, }, ], ..Default::default() }, ..Default::default() }, network_identity: NetworkIdentity { network_name: "config-net".to_owned(), network_secret: Some("secret".to_owned()), network_secret_digest: None, }, stun_info: StunInfo::default(), feature_flags: PeerFeatureFlag::default(), secure_mode: Some(SecureModeConfig { enabled: true, ..Default::default() }), host_routing: HostRoutingPolicy { local_exit_node_fallback: true, }, }; let mut flags = FlagsInConfig::default(); flags.p2p_only = true; let acl = Acl { acl_v1: Some(easytier_proto::acl::AclV1 { chains: Vec::new(), group: Some(easytier_proto::acl::GroupInfo { declares: vec![easytier_proto::acl::GroupIdentity { group_name: "ops".to_string(), group_secret: "group-secret".to_string(), }], members: vec!["ops".to_string()], }), }), }; let context = core_owned_context_with_acl(runtime.clone(), flags.clone(), None, Some(&acl)); assert_eq!(context.runtime_config().core, runtime.core); assert_eq!(context.network_identity(), runtime.network_identity); assert_eq!(context.flags(), flags); assert_eq!(context.instance_id(), instance_id); assert_eq!(context.hostname(), "config-node"); assert_eq!(context.ipv4(), Some("10.20.0.7/16".parse().unwrap())); assert_eq!(context.ipv6(), Some("2001:db8::7/64".parse().unwrap())); assert!(context.is_ip_in_same_network(&"10.20.99.1".parse().unwrap())); assert!(context.is_ip_in_same_network(&"2001:db8::99".parse().unwrap())); assert!(!context.is_ip_in_same_network(&"10.21.0.1".parse().unwrap())); assert_eq!( context.proxy_cidrs(), vec![ "10.50.0.0/16".parse().unwrap(), "10.60.0.0/16".parse().unwrap() ] ); assert!(context.secure_mode().unwrap().enabled); assert!(context.host_routing_policy().local_exit_node_fallback); let groups = context.peer_groups(7); assert_eq!(groups.len(), 1); assert_eq!(groups[0].group_name, "ops"); assert!(groups[0].verify("group-secret", 7)); assert_eq!( context.acl_group_declarations(), vec![PeerGroupIdentity { group_name: "ops".to_string(), group_secret: "group-secret".to_string(), }] ); let proof = context .secret_proof(b"challenge") .unwrap() .finalize() .into_bytes(); let expected = secret_proof_from_secret("secret", b"challenge") .unwrap() .finalize() .into_bytes(); assert_eq!(proof, expected); } #[test] fn core_owned_peer_context_uses_live_stun_source_when_injected() { struct TestStunInfoSource(StunInfo); impl PeerStunInfoSource for TestStunInfoSource { fn stun_info(&self) -> StunInfo { self.0.clone() } } let mut runtime = PeerRuntimeSnapshot::default().runtime; runtime.stun_info.tcp_nat_type = 1; let mut live = StunInfo::default(); live.tcp_nat_type = 4; let context = core_owned_context( runtime, FlagsInConfig::default(), Some(Arc::new(TestStunInfoSource(live.clone()))), ); assert_eq!(context.stun_info(), live); assert_eq!(context.runtime_config().stun_info, live); } fn core_owned_context( runtime: PeerRuntimeConfig, flags: FlagsInConfig, stun_info_source: Option>, ) -> CorePeerContext { core_owned_context_with_acl(runtime, flags, stun_info_source, None) } fn core_owned_context_with_acl( runtime: PeerRuntimeConfig, flags: FlagsInConfig, stun_info_source: Option>, acl: Option<&Acl>, ) -> CorePeerContext { let mut snapshot = PeerRuntimeSnapshot::new(runtime, flags); snapshot.set_acl_groups(acl); let config = CoreRuntimeConfigStore::new(CoreRuntimeConfig::default(), Arc::new(snapshot)); CorePeerContext::new( config, Arc::new(()), CorePeerContextAdapters { stun_info_source, events: Arc::new(()), credential_storage: None, }, ) } fn core_context_with_routes(ipv4: Option, ipv6: Option) -> CorePeerContext { core_owned_context( PeerRuntimeConfig { core: CoreConfig { routes: RouteConfig { ipv4, ipv6, ..Default::default() }, ..Default::default() }, network_identity: NetworkIdentity::default(), stun_info: StunInfo::default(), feature_flags: PeerFeatureFlag::default(), secure_mode: None, host_routing: HostRoutingPolicy::default(), }, FlagsInConfig::default(), None, ) } #[tokio::test] async fn core_owned_peer_context_publishes_peer_events_per_instance() { let context = core_context_with_routes(None, None); let mut events = context.subscribe_peer_events().unwrap(); context.issue_event(PeerEvent::PeerAdded(7)); assert_eq!(events.recv().await.unwrap(), PeerContextEvent::PeerAdded(7)); context.issue_event(PeerEvent::PeerConnAdded(Default::default())); assert_eq!( events.recv().await.unwrap(), PeerContextEvent::PeerConnAdded ); context.issue_event(PeerEvent::PeerConnRemoved(Default::default())); assert_eq!( events.recv().await.unwrap(), PeerContextEvent::PeerConnRemoved ); context.issue_event(PeerEvent::PeerRemoved(7)); assert_eq!( events.recv().await.unwrap(), PeerContextEvent::PeerRemoved(7) ); } #[test] fn core_owned_peer_context_validates_route_family_and_prefix_edges() { let mismatched = core_context_with_routes( Some(IpPrefix { address: "2001:db8::1".parse().unwrap(), prefix_len: 64, }), Some(IpPrefix { address: "10.20.0.1".parse().unwrap(), prefix_len: 24, }), ); assert_eq!(mismatched.ipv4(), None); assert_eq!(mismatched.ipv6(), None); assert!(!mismatched.is_ip_in_same_network(&"2001:db8::2".parse().unwrap())); assert!(!mismatched.is_ip_in_same_network(&"10.20.0.2".parse().unwrap())); let edges = core_context_with_routes( Some(IpPrefix::new("10.20.0.7".parse().unwrap(), 0).unwrap()), Some(IpPrefix::new("2001:db8::7".parse().unwrap(), 128).unwrap()), ); assert!(edges.is_ip_in_same_network(&"203.0.113.1".parse().unwrap())); assert!(edges.is_ip_in_same_network(&"2001:db8::7".parse().unwrap())); assert!(!edges.is_ip_in_same_network(&"2001:db8::8".parse().unwrap())); let invalid = core_context_with_routes( Some(IpPrefix { address: "10.20.0.7".parse().unwrap(), prefix_len: 33, }), None, ); assert_eq!(invalid.ipv4(), None); assert!(!invalid.is_ip_in_same_network(&"10.20.0.7".parse().unwrap())); } #[test] fn trusted_key_manager_respects_source_filter() { let manager = TrustedKeyMapManager::new(); let network_name = "net"; let pubkey = vec![1; 32]; manager.update_trusted_keys( network_name, HashMap::from([( pubkey.clone(), TrustedKeyMetadata { source: TrustedKeySource::OspfCredential, expiry_unix: None, }, )]), ); assert!(manager.verify_trusted_key(&pubkey, network_name)); assert!(manager.verify_trusted_key_with_source( &pubkey, network_name, Some(TrustedKeySource::OspfCredential), )); assert!(!manager.verify_trusted_key_with_source( &pubkey, network_name, Some(TrustedKeySource::OspfNode), )); } }