mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-16 02:45:41 +00:00
fix: refresh ACL groups and enable TCP_NODELAY for WebSocket (#2118)
* fix: refresh ACL groups and enable TCP_NODELAY for WebSocket * add remove_peers to remove list of peer id in ospf route * fix secure tunnel for unreliable udp tunnel * fix(web-client): timeout secure tunnel handshake * fix(web-server): tolerate delayed secure hello * fix quic endpoint panic * fix replay check
This commit is contained in:
@@ -418,6 +418,32 @@ impl Debug for SyncedRouteInfo {
|
||||
}
|
||||
|
||||
impl SyncedRouteInfo {
|
||||
fn set_peer_groups(&self, peer_id: PeerId, groups: HashMap<String, Vec<u8>>) {
|
||||
if groups.is_empty() {
|
||||
self.group_trust_map.remove(&peer_id);
|
||||
self.group_trust_map_cache.remove(&peer_id);
|
||||
return;
|
||||
}
|
||||
|
||||
let group_names = groups.keys().cloned().collect();
|
||||
self.group_trust_map.insert(peer_id, groups);
|
||||
self.group_trust_map_cache
|
||||
.insert(peer_id, Arc::new(group_names));
|
||||
}
|
||||
|
||||
fn get_proof_groups(&self, peer_id: PeerId) -> HashMap<String, Vec<u8>> {
|
||||
self.group_trust_map
|
||||
.get(&peer_id)
|
||||
.map(|groups| {
|
||||
groups
|
||||
.iter()
|
||||
.filter(|(_, proof)| !proof.is_empty())
|
||||
.map(|(group, proof)| (group.clone(), proof.clone()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn mark_credential_peer(info: &mut RoutePeerInfo, is_credential_peer: bool) {
|
||||
let mut feature_flag = info.feature_flag.unwrap_or_default();
|
||||
feature_flag.is_credential_peer = is_credential_peer;
|
||||
@@ -439,13 +465,38 @@ impl SyncedRouteInfo {
|
||||
}
|
||||
|
||||
fn remove_peer(&self, peer_id: PeerId) {
|
||||
tracing::warn!(?peer_id, "remove_peer from synced_route_info");
|
||||
self.peer_infos.write().remove(&peer_id);
|
||||
self.raw_peer_infos.remove(&peer_id);
|
||||
self.conn_map.write().remove(&peer_id);
|
||||
self.foreign_network.retain(|k, _| k.peer_id != peer_id);
|
||||
self.group_trust_map.remove(&peer_id);
|
||||
self.group_trust_map_cache.remove(&peer_id);
|
||||
self.remove_peers([peer_id]);
|
||||
}
|
||||
|
||||
fn remove_peers<I>(&self, peer_ids: I)
|
||||
where
|
||||
I: IntoIterator<Item = PeerId>,
|
||||
{
|
||||
let peer_ids: HashSet<_> = peer_ids.into_iter().collect();
|
||||
if peer_ids.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
for peer_id in &peer_ids {
|
||||
tracing::warn!(?peer_id, "remove_peer from synced_route_info");
|
||||
}
|
||||
|
||||
{
|
||||
let mut peer_infos = self.peer_infos.write();
|
||||
let mut conn_map = self.conn_map.write();
|
||||
for peer_id in &peer_ids {
|
||||
peer_infos.remove(peer_id);
|
||||
conn_map.remove(peer_id);
|
||||
}
|
||||
}
|
||||
|
||||
for peer_id in &peer_ids {
|
||||
self.raw_peer_infos.remove(peer_id);
|
||||
self.group_trust_map.remove(peer_id);
|
||||
self.group_trust_map_cache.remove(peer_id);
|
||||
}
|
||||
self.foreign_network
|
||||
.retain(|k, _| !peer_ids.contains(&k.peer_id));
|
||||
|
||||
shrink_dashmap(&self.raw_peer_infos, None);
|
||||
shrink_dashmap(&self.foreign_network, None);
|
||||
@@ -827,31 +878,20 @@ impl SyncedRouteInfo {
|
||||
&self,
|
||||
peer_infos: &[RoutePeerInfo],
|
||||
local_group_declarations: &[GroupIdentity],
|
||||
trust_admin_groups_without_proof: bool,
|
||||
) {
|
||||
let local_group_declarations = local_group_declarations
|
||||
.iter()
|
||||
.map(|g| (g.group_name.as_str(), g.group_secret.as_str()))
|
||||
.collect::<std::collections::HashMap<&str, &str>>();
|
||||
|
||||
let verify_groups = |old_trusted_groups: Option<&HashMap<String, Vec<u8>>>,
|
||||
info: &RoutePeerInfo|
|
||||
-> HashMap<String, Vec<u8>> {
|
||||
let verify_groups = |info: &RoutePeerInfo| -> HashMap<String, Vec<u8>> {
|
||||
let mut trusted_groups_for_peer: HashMap<String, Vec<u8>> = HashMap::new();
|
||||
|
||||
for group_proof in &info.groups {
|
||||
let name = &group_proof.group_name;
|
||||
let proof_bytes = group_proof.group_proof.clone();
|
||||
|
||||
// If we already trusted this group and the proof hasn't changed, reuse it.
|
||||
if old_trusted_groups
|
||||
.and_then(|g| g.get(name))
|
||||
.map(|old| old == &proof_bytes)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
trusted_groups_for_peer.insert(name.clone(), proof_bytes);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(&local_secret) =
|
||||
local_group_declarations.get(group_proof.group_name.as_str())
|
||||
{
|
||||
@@ -867,34 +907,39 @@ impl SyncedRouteInfo {
|
||||
}
|
||||
}
|
||||
|
||||
if trust_admin_groups_without_proof && self.is_admin_peer(info) {
|
||||
for group_proof in &info.groups {
|
||||
trusted_groups_for_peer
|
||||
.entry(group_proof.group_name.clone())
|
||||
.or_default();
|
||||
}
|
||||
}
|
||||
|
||||
trusted_groups_for_peer
|
||||
};
|
||||
|
||||
for info in peer_infos {
|
||||
match self.group_trust_map.entry(info.peer_id) {
|
||||
dashmap::mapref::entry::Entry::Occupied(mut entry) => {
|
||||
let old_trusted_groups = entry.get().clone();
|
||||
let trusted_groups_for_peer = verify_groups(Some(&old_trusted_groups), info);
|
||||
let trusted_groups_for_peer = verify_groups(info);
|
||||
|
||||
if trusted_groups_for_peer.is_empty() {
|
||||
entry.remove();
|
||||
self.group_trust_map_cache.remove(&info.peer_id);
|
||||
} else {
|
||||
self.group_trust_map_cache.insert(
|
||||
info.peer_id,
|
||||
Arc::new(trusted_groups_for_peer.keys().cloned().collect()),
|
||||
);
|
||||
let group_names = trusted_groups_for_peer.keys().cloned().collect();
|
||||
self.group_trust_map_cache
|
||||
.insert(info.peer_id, Arc::new(group_names));
|
||||
*entry.get_mut() = trusted_groups_for_peer;
|
||||
}
|
||||
}
|
||||
dashmap::mapref::entry::Entry::Vacant(entry) => {
|
||||
let trusted_groups_for_peer = verify_groups(None, info);
|
||||
let trusted_groups_for_peer = verify_groups(info);
|
||||
|
||||
if !trusted_groups_for_peer.is_empty() {
|
||||
self.group_trust_map_cache.insert(
|
||||
info.peer_id,
|
||||
Arc::new(trusted_groups_for_peer.keys().cloned().collect()),
|
||||
);
|
||||
let group_names = trusted_groups_for_peer.keys().cloned().collect();
|
||||
self.group_trust_map_cache
|
||||
.insert(info.peer_id, Arc::new(group_names));
|
||||
entry.insert(trusted_groups_for_peer);
|
||||
}
|
||||
}
|
||||
@@ -904,16 +949,12 @@ impl SyncedRouteInfo {
|
||||
|
||||
fn update_my_group_trusts(&self, my_peer_id: PeerId, groups: &[PeerGroupInfo]) {
|
||||
let mut my_group_map = HashMap::new();
|
||||
let mut my_group_names = Vec::new();
|
||||
|
||||
for group in groups.iter() {
|
||||
my_group_map.insert(group.group_name.clone(), group.group_proof.clone());
|
||||
my_group_names.push(group.group_name.clone());
|
||||
}
|
||||
|
||||
self.group_trust_map.insert(my_peer_id, my_group_map);
|
||||
self.group_trust_map_cache
|
||||
.insert(my_peer_id, Arc::new(my_group_names));
|
||||
self.set_peer_groups(my_peer_id, my_group_map);
|
||||
}
|
||||
|
||||
/// Collect trusted credential pubkeys from admin nodes (network_secret holders)
|
||||
@@ -1004,18 +1045,13 @@ impl SyncedRouteInfo {
|
||||
continue;
|
||||
}
|
||||
if let Some(tc) = all_trusted.get(&info.noise_static_pubkey) {
|
||||
// This peer is a credential peer, assign groups from credential declaration
|
||||
if !tc.groups.is_empty() {
|
||||
let mut group_map = HashMap::new();
|
||||
let mut group_names = Vec::new();
|
||||
for g in &tc.groups {
|
||||
group_map.insert(g.clone(), Vec::new()); // no proof needed, admin-declared
|
||||
group_names.push(g.clone());
|
||||
}
|
||||
self.group_trust_map.insert(info.peer_id, group_map);
|
||||
self.group_trust_map_cache
|
||||
.insert(info.peer_id, Arc::new(group_names));
|
||||
// Start from proof-backed groups so credential-declared groups can coexist
|
||||
// without leaving stale credential-only entries behind after refreshes.
|
||||
let mut group_map = self.get_proof_groups(info.peer_id);
|
||||
for g in &tc.groups {
|
||||
group_map.entry(g.clone()).or_default();
|
||||
}
|
||||
self.set_peer_groups(info.peer_id, group_map);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1039,19 +1075,10 @@ impl SyncedRouteInfo {
|
||||
// Remove untrusted peers from peer_infos so they won't appear in route graph
|
||||
if !untrusted_peers.is_empty() {
|
||||
drop(peer_infos); // release read lock before writing
|
||||
let mut peer_infos_write = self.peer_infos.write();
|
||||
for peer_id in &untrusted_peers {
|
||||
tracing::warn!(?peer_id, "removing untrusted peer from route info");
|
||||
peer_infos_write.remove(peer_id);
|
||||
self.raw_peer_infos.remove(peer_id);
|
||||
}
|
||||
drop(peer_infos_write);
|
||||
// Also remove from conn_map
|
||||
let mut conn_map = self.conn_map.write();
|
||||
for peer_id in &untrusted_peers {
|
||||
conn_map.remove(peer_id);
|
||||
}
|
||||
self.version.inc();
|
||||
self.remove_peers(untrusted_peers.iter().copied());
|
||||
}
|
||||
|
||||
(untrusted_peers, global_trusted_keys)
|
||||
@@ -2444,14 +2471,53 @@ impl PeerRouteServiceImpl {
|
||||
my_peer_info_updated || my_conn_info_updated || my_foreign_network_updated
|
||||
}
|
||||
|
||||
async fn refresh_credential_trusts_and_disconnect(&self) -> bool {
|
||||
async fn refresh_acl_groups(&self) -> bool {
|
||||
let my_peer_info_updated = self.update_my_peer_info();
|
||||
let trust_admin_groups_without_proof = self
|
||||
.global_ctx
|
||||
.get_network_identity()
|
||||
.network_secret
|
||||
.is_none();
|
||||
|
||||
let peer_infos: Vec<_> = self
|
||||
.synced_route_info
|
||||
.peer_infos
|
||||
.read()
|
||||
.iter()
|
||||
.map(|(_, info)| info.clone())
|
||||
.collect();
|
||||
self.synced_route_info.verify_and_update_group_trusts(
|
||||
&peer_infos,
|
||||
&self.global_ctx.get_acl_group_declarations(),
|
||||
trust_admin_groups_without_proof,
|
||||
);
|
||||
|
||||
let untrusted = self.refresh_credential_trusts();
|
||||
self.disconnect_untrusted_peers(&untrusted).await;
|
||||
|
||||
if my_peer_info_updated || !untrusted.is_empty() {
|
||||
self.update_route_table_and_cached_local_conn_bitmap();
|
||||
self.update_foreign_network_owner_map();
|
||||
}
|
||||
if my_peer_info_updated {
|
||||
self.update_peer_info_last_update();
|
||||
}
|
||||
|
||||
my_peer_info_updated || !untrusted.is_empty()
|
||||
}
|
||||
|
||||
fn refresh_credential_trusts(&self) -> Vec<PeerId> {
|
||||
let network_identity = self.global_ctx.get_network_identity();
|
||||
let network_secret = network_identity.network_secret.as_deref();
|
||||
let (untrusted, global_trusted_keys) = self
|
||||
.synced_route_info
|
||||
.verify_and_update_credential_trusts(network_secret);
|
||||
.verify_and_update_credential_trusts(network_identity.network_secret.as_deref());
|
||||
self.global_ctx
|
||||
.update_trusted_keys(global_trusted_keys, &network_identity.network_name);
|
||||
untrusted
|
||||
}
|
||||
|
||||
async fn refresh_credential_trusts_and_disconnect(&self) -> bool {
|
||||
let untrusted = self.refresh_credential_trusts();
|
||||
self.disconnect_untrusted_peers(&untrusted).await;
|
||||
!untrusted.is_empty()
|
||||
}
|
||||
@@ -2529,9 +2595,8 @@ impl PeerRouteServiceImpl {
|
||||
}
|
||||
}
|
||||
|
||||
for p in to_remove.iter() {
|
||||
self.synced_route_info.remove_peer(*p);
|
||||
}
|
||||
self.synced_route_info
|
||||
.remove_peers(to_remove.iter().copied());
|
||||
|
||||
// clear expired foreign network info
|
||||
let mut to_remove = Vec::new();
|
||||
@@ -3197,6 +3262,11 @@ impl RouteSessionManager {
|
||||
(peer_infos, raw_peer_infos.as_ref().unwrap())
|
||||
};
|
||||
if !pi.is_empty() {
|
||||
let trust_admin_groups_without_proof = service_impl
|
||||
.global_ctx
|
||||
.get_network_identity()
|
||||
.network_secret
|
||||
.is_none();
|
||||
service_impl.synced_route_info.update_peer_infos(
|
||||
my_peer_id,
|
||||
service_impl.my_peer_route_id,
|
||||
@@ -3209,6 +3279,7 @@ impl RouteSessionManager {
|
||||
.verify_and_update_group_trusts(
|
||||
pi,
|
||||
&service_impl.global_ctx.get_acl_group_declarations(),
|
||||
trust_admin_groups_without_proof,
|
||||
);
|
||||
session.update_dst_saved_peer_info_version(pi, from_peer_id);
|
||||
need_update_route_table = true;
|
||||
@@ -3228,15 +3299,7 @@ impl RouteSessionManager {
|
||||
|
||||
if need_update_route_table {
|
||||
// Run credential verification and update route table
|
||||
let network_identity = service_impl.global_ctx.get_network_identity();
|
||||
let (untrusted, global_trusted_keys) = service_impl
|
||||
.synced_route_info
|
||||
.verify_and_update_credential_trusts(network_identity.network_secret.as_deref());
|
||||
untrusted_peers = untrusted;
|
||||
// Sync trusted keys to GlobalCtx for handshake verification
|
||||
service_impl
|
||||
.global_ctx
|
||||
.update_trusted_keys(global_trusted_keys, &network_identity.network_name);
|
||||
untrusted_peers = service_impl.refresh_credential_trusts();
|
||||
service_impl.update_route_table_and_cached_local_conn_bitmap();
|
||||
}
|
||||
|
||||
@@ -3644,6 +3707,12 @@ impl Route for PeerRoute {
|
||||
fn get_peer_groups(&self, peer_id: PeerId) -> Arc<Vec<String>> {
|
||||
self.service_impl.get_peer_groups(peer_id)
|
||||
}
|
||||
|
||||
async fn refresh_acl_groups(&self) {
|
||||
if self.service_impl.refresh_acl_groups().await {
|
||||
self.session_mgr.sync_now("refresh_acl_groups");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PeerPacketFilter for Arc<PeerRoute> {}
|
||||
@@ -3665,11 +3734,14 @@ mod tests {
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use super::{PeerRoute, REMOVE_DEAD_PEER_INFO_AFTER};
|
||||
use super::{PeerRoute, REMOVE_DEAD_PEER_INFO_AFTER, RouteConnInfo};
|
||||
use crate::{
|
||||
common::{
|
||||
PeerId,
|
||||
global_ctx::{GlobalCtxEvent, TrustedKeySource, tests::get_mock_global_ctx},
|
||||
global_ctx::{
|
||||
GlobalCtxEvent, TrustedKeySource,
|
||||
tests::{get_mock_global_ctx, get_mock_global_ctx_with_network},
|
||||
},
|
||||
},
|
||||
connector::udp_hole_punch::tests::replace_stun_info_collector,
|
||||
peers::{
|
||||
@@ -3680,8 +3752,10 @@ mod tests {
|
||||
tests::{connect_peer_manager, create_mock_peer_manager, wait_route_appear},
|
||||
},
|
||||
proto::{
|
||||
acl::{Acl, AclV1, GroupIdentity, GroupInfo},
|
||||
common::{NatType, PeerFeatureFlag},
|
||||
peer_rpc::{
|
||||
ForeignNetworkRouteInfoEntry, ForeignNetworkRouteInfoKey, PeerGroupInfo,
|
||||
PeerIdentityType, RoutePeerInfo, RoutePeerInfos, SyncRouteInfoRequest,
|
||||
TrustedCredentialPubkey, TrustedCredentialPubkeyProof,
|
||||
},
|
||||
@@ -4040,6 +4114,219 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn credential_groups_merge_with_proof_groups_and_recompute_cleanly() {
|
||||
let service_impl = PeerRouteServiceImpl::new(1, get_mock_global_ctx());
|
||||
let network_secret = "sec1";
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
let credential_peer_id = 31;
|
||||
let credential_pubkey = vec![7; 32];
|
||||
|
||||
let mut credential_info = RoutePeerInfo::new();
|
||||
credential_info.peer_id = credential_peer_id;
|
||||
credential_info.version = 1;
|
||||
credential_info.noise_static_pubkey = credential_pubkey.clone();
|
||||
credential_info.groups = vec![PeerGroupInfo::generate_with_proof(
|
||||
"proof-group".to_string(),
|
||||
"proof-secret".to_string(),
|
||||
credential_peer_id,
|
||||
)];
|
||||
|
||||
let mut admin_info = RoutePeerInfo::new();
|
||||
admin_info.peer_id = 32;
|
||||
admin_info.version = 1;
|
||||
admin_info.feature_flag = Some(PeerFeatureFlag {
|
||||
is_credential_peer: false,
|
||||
..Default::default()
|
||||
});
|
||||
admin_info.trusted_credential_pubkeys = vec![TrustedCredentialPubkeyProof::new_signed(
|
||||
TrustedCredentialPubkey {
|
||||
pubkey: credential_pubkey.clone(),
|
||||
groups: vec!["cred-group".to_string()],
|
||||
expiry_unix: now + 600,
|
||||
..Default::default()
|
||||
},
|
||||
network_secret,
|
||||
)];
|
||||
|
||||
{
|
||||
let mut guard = service_impl.synced_route_info.peer_infos.write();
|
||||
guard.insert(admin_info.peer_id, admin_info.clone());
|
||||
guard.insert(credential_peer_id, credential_info.clone());
|
||||
}
|
||||
|
||||
service_impl
|
||||
.synced_route_info
|
||||
.verify_and_update_group_trusts(
|
||||
&[credential_info],
|
||||
&[GroupIdentity {
|
||||
group_name: "proof-group".to_string(),
|
||||
group_secret: "proof-secret".to_string(),
|
||||
}],
|
||||
false,
|
||||
);
|
||||
service_impl
|
||||
.synced_route_info
|
||||
.verify_and_update_credential_trusts(Some(network_secret));
|
||||
|
||||
let groups = service_impl.get_peer_groups(credential_peer_id);
|
||||
assert!(groups.contains(&"proof-group".to_string()));
|
||||
assert!(groups.contains(&"cred-group".to_string()));
|
||||
|
||||
let guard = service_impl.synced_route_info.peer_infos.write();
|
||||
let admin_info = guard.get(&32).unwrap().clone();
|
||||
drop(guard);
|
||||
|
||||
let mut updated_admin = admin_info;
|
||||
updated_admin.trusted_credential_pubkeys = vec![TrustedCredentialPubkeyProof::new_signed(
|
||||
TrustedCredentialPubkey {
|
||||
pubkey: credential_pubkey.clone(),
|
||||
groups: vec!["replacement-group".to_string()],
|
||||
expiry_unix: now + 600,
|
||||
..Default::default()
|
||||
},
|
||||
network_secret,
|
||||
)];
|
||||
service_impl
|
||||
.synced_route_info
|
||||
.peer_infos
|
||||
.write()
|
||||
.insert(updated_admin.peer_id, updated_admin);
|
||||
|
||||
service_impl
|
||||
.synced_route_info
|
||||
.verify_and_update_credential_trusts(Some(network_secret));
|
||||
|
||||
let groups = service_impl.get_peer_groups(credential_peer_id);
|
||||
assert!(groups.contains(&"proof-group".to_string()));
|
||||
assert!(groups.contains(&"replacement-group".to_string()));
|
||||
assert!(!groups.contains(&"cred-group".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remove_peers_batches_cleanup_and_version_increment() {
|
||||
let service_impl = PeerRouteServiceImpl::new(1, get_mock_global_ctx());
|
||||
let removed_peer_ids = [41, 42];
|
||||
let retained_peer_id = 43;
|
||||
|
||||
{
|
||||
let mut peer_infos = service_impl.synced_route_info.peer_infos.write();
|
||||
let mut conn_map = service_impl.synced_route_info.conn_map.write();
|
||||
for peer_id in removed_peer_ids {
|
||||
let mut info = RoutePeerInfo::new();
|
||||
info.peer_id = peer_id;
|
||||
info.version = 1;
|
||||
peer_infos.insert(peer_id, info);
|
||||
conn_map.insert(peer_id, RouteConnInfo::default());
|
||||
}
|
||||
|
||||
let mut retained_info = RoutePeerInfo::new();
|
||||
retained_info.peer_id = retained_peer_id;
|
||||
retained_info.version = 1;
|
||||
peer_infos.insert(retained_peer_id, retained_info);
|
||||
conn_map.insert(retained_peer_id, RouteConnInfo::default());
|
||||
}
|
||||
|
||||
for peer_id in removed_peer_ids {
|
||||
service_impl.synced_route_info.raw_peer_infos.insert(
|
||||
peer_id,
|
||||
DynamicMessage::new(RoutePeerInfo::default().descriptor()),
|
||||
);
|
||||
service_impl.synced_route_info.group_trust_map.insert(
|
||||
peer_id,
|
||||
HashMap::from([("guest".to_string(), vec![1, 2, 3])]),
|
||||
);
|
||||
service_impl
|
||||
.synced_route_info
|
||||
.group_trust_map_cache
|
||||
.insert(peer_id, Arc::new(vec!["guest".to_string()]));
|
||||
service_impl.synced_route_info.foreign_network.insert(
|
||||
ForeignNetworkRouteInfoKey {
|
||||
peer_id,
|
||||
..Default::default()
|
||||
},
|
||||
ForeignNetworkRouteInfoEntry::default(),
|
||||
);
|
||||
}
|
||||
|
||||
service_impl.synced_route_info.foreign_network.insert(
|
||||
ForeignNetworkRouteInfoKey {
|
||||
peer_id: retained_peer_id,
|
||||
..Default::default()
|
||||
},
|
||||
ForeignNetworkRouteInfoEntry::default(),
|
||||
);
|
||||
|
||||
let initial_version = service_impl.synced_route_info.version.get();
|
||||
service_impl
|
||||
.synced_route_info
|
||||
.remove_peers(removed_peer_ids);
|
||||
|
||||
assert_eq!(
|
||||
service_impl.synced_route_info.version.get(),
|
||||
initial_version + 1
|
||||
);
|
||||
for peer_id in removed_peer_ids {
|
||||
assert!(
|
||||
!service_impl
|
||||
.synced_route_info
|
||||
.peer_infos
|
||||
.read()
|
||||
.contains_key(&peer_id)
|
||||
);
|
||||
assert!(
|
||||
!service_impl
|
||||
.synced_route_info
|
||||
.conn_map
|
||||
.read()
|
||||
.contains_key(&peer_id)
|
||||
);
|
||||
assert!(
|
||||
!service_impl
|
||||
.synced_route_info
|
||||
.raw_peer_infos
|
||||
.contains_key(&peer_id)
|
||||
);
|
||||
assert!(
|
||||
!service_impl
|
||||
.synced_route_info
|
||||
.group_trust_map
|
||||
.contains_key(&peer_id)
|
||||
);
|
||||
assert!(
|
||||
!service_impl
|
||||
.synced_route_info
|
||||
.group_trust_map_cache
|
||||
.contains_key(&peer_id)
|
||||
);
|
||||
assert!(
|
||||
!service_impl.synced_route_info.foreign_network.contains_key(
|
||||
&ForeignNetworkRouteInfoKey {
|
||||
peer_id,
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
assert!(
|
||||
service_impl
|
||||
.synced_route_info
|
||||
.peer_infos
|
||||
.read()
|
||||
.contains_key(&retained_peer_id)
|
||||
);
|
||||
assert!(service_impl.synced_route_info.foreign_network.contains_key(
|
||||
&ForeignNetworkRouteInfoKey {
|
||||
peer_id: retained_peer_id,
|
||||
..Default::default()
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sync_route_info_marks_credential_sender_and_filters_entries() {
|
||||
let peer_mgr = create_mock_pmgr().await;
|
||||
@@ -4208,6 +4495,7 @@ mod tests {
|
||||
admin_info.trusted_credential_pubkeys = vec![TrustedCredentialPubkeyProof {
|
||||
credential: Some(TrustedCredentialPubkey {
|
||||
pubkey: credential_pubkey.clone(),
|
||||
groups: vec!["guest".to_string()],
|
||||
expiry_unix: i64::MAX,
|
||||
..Default::default()
|
||||
}),
|
||||
@@ -4237,6 +4525,11 @@ mod tests {
|
||||
.trusted_credential_pubkeys
|
||||
.contains_key(&credential_pubkey)
|
||||
);
|
||||
assert!(
|
||||
service_impl
|
||||
.get_peer_groups(credential_peer_id)
|
||||
.contains(&"guest".to_string())
|
||||
);
|
||||
|
||||
service_impl.clear_expired_peer().await;
|
||||
|
||||
@@ -4260,6 +4553,299 @@ mod tests {
|
||||
.read()
|
||||
.contains_key(&credential_peer_id)
|
||||
);
|
||||
assert!(
|
||||
!service_impl
|
||||
.synced_route_info
|
||||
.group_trust_map_cache
|
||||
.contains_key(&credential_peer_id)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn refresh_acl_groups_returns_true_when_untrusted_peers_are_disconnected() {
|
||||
let service_impl = PeerRouteServiceImpl::new(1, get_mock_global_ctx());
|
||||
let credential_peer_id: PeerId = 10061;
|
||||
let credential_pubkey = vec![8u8; 32];
|
||||
let closed_peers = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
*service_impl.interface.lock().await = Some(Box::new(TrackingInterface {
|
||||
my_peer_id: service_impl.my_peer_id,
|
||||
closed_peers: closed_peers.clone(),
|
||||
}));
|
||||
|
||||
let mut credential_info = RoutePeerInfo::new();
|
||||
credential_info.peer_id = credential_peer_id;
|
||||
credential_info.version = 1;
|
||||
credential_info.noise_static_pubkey = credential_pubkey.clone();
|
||||
credential_info.feature_flag = Some(PeerFeatureFlag {
|
||||
is_credential_peer: true,
|
||||
..Default::default()
|
||||
});
|
||||
let self_info = RoutePeerInfo::new_updated_self(
|
||||
service_impl.my_peer_id,
|
||||
service_impl.my_peer_route_id,
|
||||
&service_impl.global_ctx,
|
||||
);
|
||||
let mut self_info = self_info;
|
||||
self_info.version = 1;
|
||||
self_info.last_update = Some(SystemTime::now().into());
|
||||
{
|
||||
let mut guard = service_impl.synced_route_info.peer_infos.write();
|
||||
guard.insert(service_impl.my_peer_id, self_info);
|
||||
guard.insert(credential_peer_id, credential_info);
|
||||
}
|
||||
service_impl
|
||||
.synced_route_info
|
||||
.trusted_credential_pubkeys
|
||||
.insert(
|
||||
credential_pubkey.clone(),
|
||||
TrustedCredentialPubkey {
|
||||
pubkey: credential_pubkey.clone(),
|
||||
expiry_unix: i64::MAX,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
assert!(service_impl.refresh_acl_groups().await);
|
||||
assert!(closed_peers.lock().contains(&credential_peer_id));
|
||||
assert!(
|
||||
!service_impl
|
||||
.synced_route_info
|
||||
.peer_infos
|
||||
.read()
|
||||
.contains_key(&credential_peer_id)
|
||||
);
|
||||
assert!(
|
||||
!service_impl
|
||||
.synced_route_info
|
||||
.trusted_credential_pubkeys
|
||||
.contains_key(&credential_pubkey)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn refresh_acl_groups_updates_local_membership_immediately() {
|
||||
let peer_mgr = create_mock_pmgr().await;
|
||||
let route = create_mock_route(peer_mgr.clone()).await;
|
||||
let my_peer_id = peer_mgr.my_peer_id();
|
||||
|
||||
assert!(route.service_impl.get_peer_groups(my_peer_id).is_empty());
|
||||
|
||||
peer_mgr.get_global_ctx().config.set_acl(Some(Acl {
|
||||
acl_v1: Some(AclV1 {
|
||||
group: Some(GroupInfo {
|
||||
declares: vec![GroupIdentity {
|
||||
group_name: "admin".to_string(),
|
||||
group_secret: "admin-secret".to_string(),
|
||||
}],
|
||||
members: vec!["admin".to_string()],
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
}));
|
||||
|
||||
route.refresh_acl_groups().await;
|
||||
|
||||
let groups = route.service_impl.get_peer_groups(my_peer_id);
|
||||
assert!(groups.contains(&"admin".to_string()));
|
||||
assert_eq!(groups.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn refresh_acl_groups_revalidates_cached_remote_groups() {
|
||||
let peer_mgr = create_mock_pmgr().await;
|
||||
let route = create_mock_route(peer_mgr.clone()).await;
|
||||
let remote_peer_id = 200;
|
||||
let remote_group = PeerGroupInfo::generate_with_proof(
|
||||
"ops".to_string(),
|
||||
"secret-v1".to_string(),
|
||||
remote_peer_id,
|
||||
);
|
||||
|
||||
peer_mgr.get_global_ctx().config.set_acl(Some(Acl {
|
||||
acl_v1: Some(AclV1 {
|
||||
group: Some(GroupInfo {
|
||||
declares: vec![GroupIdentity {
|
||||
group_name: "ops".to_string(),
|
||||
group_secret: "secret-v1".to_string(),
|
||||
}],
|
||||
members: vec![],
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
}));
|
||||
|
||||
let mut remote_info = RoutePeerInfo::new();
|
||||
remote_info.peer_id = remote_peer_id;
|
||||
remote_info.version = 1;
|
||||
remote_info.groups = vec![remote_group];
|
||||
route
|
||||
.service_impl
|
||||
.synced_route_info
|
||||
.peer_infos
|
||||
.write()
|
||||
.insert(remote_peer_id, remote_info.clone());
|
||||
route
|
||||
.service_impl
|
||||
.synced_route_info
|
||||
.verify_and_update_group_trusts(
|
||||
&[remote_info],
|
||||
&peer_mgr.get_global_ctx().get_acl_group_declarations(),
|
||||
false,
|
||||
);
|
||||
|
||||
assert!(
|
||||
route
|
||||
.service_impl
|
||||
.get_peer_groups(remote_peer_id)
|
||||
.contains(&"ops".to_string())
|
||||
);
|
||||
|
||||
peer_mgr.get_global_ctx().config.set_acl(Some(Acl {
|
||||
acl_v1: Some(AclV1 {
|
||||
group: Some(GroupInfo {
|
||||
declares: vec![GroupIdentity {
|
||||
group_name: "ops".to_string(),
|
||||
group_secret: "secret-v2".to_string(),
|
||||
}],
|
||||
members: vec![],
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
}));
|
||||
|
||||
route.refresh_acl_groups().await;
|
||||
|
||||
assert!(
|
||||
route
|
||||
.service_impl
|
||||
.get_peer_groups(remote_peer_id)
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn credential_verifier_trusts_admin_self_groups_from_multiple_admins() {
|
||||
let service_impl = PeerRouteServiceImpl::new(
|
||||
1,
|
||||
get_mock_global_ctx_with_network(Some(
|
||||
crate::common::config::NetworkIdentity::new_credential("net1".to_string()),
|
||||
)),
|
||||
);
|
||||
|
||||
let mut admin_a = RoutePeerInfo::new();
|
||||
admin_a.peer_id = 501;
|
||||
admin_a.version = 1;
|
||||
admin_a.groups = vec![
|
||||
PeerGroupInfo {
|
||||
group_name: "ops".to_string(),
|
||||
group_proof: vec![1; 32],
|
||||
},
|
||||
PeerGroupInfo {
|
||||
group_name: "core-admin".to_string(),
|
||||
group_proof: vec![2; 32],
|
||||
},
|
||||
];
|
||||
|
||||
let mut admin_b = RoutePeerInfo::new();
|
||||
admin_b.peer_id = 502;
|
||||
admin_b.version = 1;
|
||||
admin_b.groups = vec![PeerGroupInfo {
|
||||
group_name: "audit".to_string(),
|
||||
group_proof: vec![3; 32],
|
||||
}];
|
||||
|
||||
service_impl
|
||||
.synced_route_info
|
||||
.verify_and_update_group_trusts(&[admin_a.clone(), admin_b.clone()], &[], true);
|
||||
|
||||
let admin_a_groups = service_impl.get_peer_groups(admin_a.peer_id);
|
||||
assert!(admin_a_groups.contains(&"ops".to_string()));
|
||||
assert!(admin_a_groups.contains(&"core-admin".to_string()));
|
||||
|
||||
let admin_b_groups = service_impl.get_peer_groups(admin_b.peer_id);
|
||||
assert!(admin_b_groups.contains(&"audit".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn credential_verifier_still_checks_credential_self_declared_groups() {
|
||||
let service_impl = PeerRouteServiceImpl::new(
|
||||
1,
|
||||
get_mock_global_ctx_with_network(Some(
|
||||
crate::common::config::NetworkIdentity::new_credential("net1".to_string()),
|
||||
)),
|
||||
);
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
let credential_peer_id = 601;
|
||||
let credential_pubkey = vec![9; 32];
|
||||
|
||||
let mut admin_info = RoutePeerInfo::new();
|
||||
admin_info.peer_id = 600;
|
||||
admin_info.version = 1;
|
||||
admin_info.trusted_credential_pubkeys = vec![TrustedCredentialPubkeyProof {
|
||||
credential: Some(TrustedCredentialPubkey {
|
||||
pubkey: credential_pubkey.clone(),
|
||||
groups: vec!["cred-acl".to_string()],
|
||||
expiry_unix: now + 600,
|
||||
..Default::default()
|
||||
}),
|
||||
credential_hmac: vec![7; 32],
|
||||
}];
|
||||
|
||||
let mut credential_info = RoutePeerInfo::new();
|
||||
credential_info.peer_id = credential_peer_id;
|
||||
credential_info.version = 1;
|
||||
credential_info.noise_static_pubkey = credential_pubkey.clone();
|
||||
credential_info.feature_flag = Some(PeerFeatureFlag {
|
||||
is_credential_peer: true,
|
||||
..Default::default()
|
||||
});
|
||||
credential_info.groups = vec![
|
||||
PeerGroupInfo::generate_with_proof(
|
||||
"proof-group".to_string(),
|
||||
"proof-secret".to_string(),
|
||||
credential_peer_id,
|
||||
),
|
||||
PeerGroupInfo::generate_with_proof(
|
||||
"invalid-group".to_string(),
|
||||
"wrong-secret".to_string(),
|
||||
credential_peer_id,
|
||||
),
|
||||
];
|
||||
|
||||
{
|
||||
let mut guard = service_impl.synced_route_info.peer_infos.write();
|
||||
guard.insert(admin_info.peer_id, admin_info.clone());
|
||||
guard.insert(credential_info.peer_id, credential_info.clone());
|
||||
}
|
||||
|
||||
service_impl
|
||||
.synced_route_info
|
||||
.verify_and_update_group_trusts(
|
||||
&[admin_info, credential_info],
|
||||
&[
|
||||
GroupIdentity {
|
||||
group_name: "proof-group".to_string(),
|
||||
group_secret: "proof-secret".to_string(),
|
||||
},
|
||||
GroupIdentity {
|
||||
group_name: "invalid-group".to_string(),
|
||||
group_secret: "actual-secret".to_string(),
|
||||
},
|
||||
],
|
||||
true,
|
||||
);
|
||||
service_impl
|
||||
.synced_route_info
|
||||
.verify_and_update_credential_trusts(None);
|
||||
|
||||
let groups = service_impl.get_peer_groups(credential_peer_id);
|
||||
assert!(groups.contains(&"proof-group".to_string()));
|
||||
assert!(groups.contains(&"cred-acl".to_string()));
|
||||
assert!(!groups.contains(&"invalid-group".to_string()));
|
||||
}
|
||||
|
||||
#[rstest::rstest]
|
||||
|
||||
Reference in New Issue
Block a user