mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 02:09:06 +00:00
multi_fix: harden peer/session handling, tighten foreign-network trust, and improve web client metadata (#1999)
* machine-id should be scoped unbder same user-id * feat: report device os metadata to console * fix sync root key cause packet loss * fix tun packet not invalid * fix faketcp cause lat jitter * fix some packet not decrypt * fix peer info patch, improve performance of update self info * fix foreign credential identity mismatch handling
This commit is contained in:
@@ -303,6 +303,26 @@ impl ForeignNetworkEntry {
|
||||
fn my_peer_id(&self) -> PeerId {
|
||||
self.my_peer_id
|
||||
}
|
||||
|
||||
fn need_periodic_requery_peers(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
async fn get_peer_identity_type(&self, peer_id: PeerId) -> Option<PeerIdentityType> {
|
||||
let peer_map = self.peer_map.upgrade()?;
|
||||
peer_map.get_peer_identity_type(peer_id)
|
||||
}
|
||||
|
||||
async fn get_peer_public_key(&self, peer_id: PeerId) -> Option<Vec<u8>> {
|
||||
let peer_map = self.peer_map.upgrade()?;
|
||||
peer_map.get_peer_public_key(peer_id)
|
||||
}
|
||||
|
||||
async fn close_peer(&self, peer_id: PeerId) {
|
||||
if let Some(peer_map) = self.peer_map.upgrade() {
|
||||
let _ = peer_map.close_peer(peer_id).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let route = PeerRoute::new(
|
||||
@@ -373,15 +393,15 @@ impl ForeignNetworkEntry {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !peer_map.has_peer(from_peer_id) && relay_peer_map.is_secure_mode_enabled() {
|
||||
if relay_peer_map.is_secure_mode_enabled() && hdr.is_encrypted() {
|
||||
match relay_peer_map.decrypt_if_needed(&mut zc_packet).await {
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
tracing::error!("relay session not found");
|
||||
tracing::error!("secure session not found");
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(?e, "relay decrypt failed");
|
||||
tracing::error!(?e, "secure decrypt failed");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -620,14 +640,27 @@ pub struct ForeignNetworkManager {
|
||||
}
|
||||
|
||||
impl ForeignNetworkManager {
|
||||
async fn is_shared_pubkey_trusted(
|
||||
fn network_secret_digest_is_empty(network: &NetworkIdentity) -> bool {
|
||||
network
|
||||
.network_secret_digest
|
||||
.as_ref()
|
||||
.is_none_or(|d| d.iter().all(|b| *b == 0))
|
||||
}
|
||||
|
||||
fn should_reject_credential_trust_path(identity_type: PeerIdentityType) -> bool {
|
||||
matches!(identity_type, PeerIdentityType::Admin)
|
||||
}
|
||||
|
||||
async fn is_credential_pubkey_trusted(
|
||||
entry: &ForeignNetworkEntry,
|
||||
remote_static_pubkey: &[u8],
|
||||
) -> bool {
|
||||
remote_static_pubkey.len() == 32
|
||||
&& entry
|
||||
.global_ctx
|
||||
.is_pubkey_trusted(remote_static_pubkey, &entry.network.network_name)
|
||||
&& entry.global_ctx.is_pubkey_trusted_with_source(
|
||||
remote_static_pubkey,
|
||||
&entry.network.network_name,
|
||||
TrustedKeySource::OspfCredential,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_trusted_key_items(entry: &ForeignNetworkEntry) -> Vec<TrustedKeyInfoPb> {
|
||||
@@ -697,6 +730,20 @@ impl ForeignNetworkManager {
|
||||
return ret;
|
||||
}
|
||||
|
||||
let peer_digest_empty = Self::network_secret_digest_is_empty(&peer_network);
|
||||
if peer_digest_empty
|
||||
&& self
|
||||
.data
|
||||
.get_network_entry(&peer_network.network_name)
|
||||
.is_none()
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"foreign network {} is not established by a secret-verified peer yet",
|
||||
peer_network.network_name
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let (entry, new_added) = self
|
||||
.data
|
||||
.get_or_insert_entry(
|
||||
@@ -711,13 +758,17 @@ impl ForeignNetworkManager {
|
||||
.await;
|
||||
|
||||
let same_identity = entry.network == peer_network;
|
||||
let shared_peer = peer_conn.get_peer_identity_type() == PeerIdentityType::SharedNode;
|
||||
let shared_peer_trusted = shared_peer
|
||||
&& Self::is_shared_pubkey_trusted(&entry, &conn_info.noise_remote_static_pubkey).await;
|
||||
let peer_identity_type = peer_conn.get_peer_identity_type();
|
||||
let credential_peer_trusted = peer_digest_empty
|
||||
&& Self::is_credential_pubkey_trusted(&entry, &conn_info.noise_remote_static_pubkey)
|
||||
.await;
|
||||
let credential_identity_mismatch = credential_peer_trusted
|
||||
&& Self::should_reject_credential_trust_path(peer_identity_type);
|
||||
|
||||
let _g = entry.lock.lock().await;
|
||||
|
||||
if (!(same_identity || shared_peer_trusted))
|
||||
if (!(same_identity || credential_peer_trusted))
|
||||
|| credential_identity_mismatch
|
||||
|| entry.my_peer_id != peer_conn.get_my_peer_id()
|
||||
{
|
||||
if new_added {
|
||||
@@ -730,13 +781,18 @@ impl ForeignNetworkManager {
|
||||
entry.my_peer_id,
|
||||
peer_conn.get_my_peer_id()
|
||||
)
|
||||
} else if credential_identity_mismatch {
|
||||
anyhow::anyhow!(
|
||||
"credential-trusted foreign peer has invalid identity type: {:?}",
|
||||
peer_identity_type
|
||||
)
|
||||
} else {
|
||||
anyhow::anyhow!(
|
||||
"foreign peer identity not trusted. exp: {:?} real: {:?}, remote_pubkey_len: {}, shared_trusted: {}",
|
||||
"foreign peer identity not trusted. exp: {:?} real: {:?}, remote_pubkey_len: {}, credential_trusted: {}",
|
||||
entry.network,
|
||||
peer_network,
|
||||
conn_info.noise_remote_static_pubkey.len(),
|
||||
shared_peer_trusted,
|
||||
credential_peer_trusted,
|
||||
)
|
||||
};
|
||||
tracing::error!(?err, "foreign network entry not match, disconnect peer");
|
||||
@@ -911,7 +967,7 @@ pub mod tests {
|
||||
set_global_var,
|
||||
tunnel::common::tests::wait_for_condition,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -933,6 +989,20 @@ pub mod tests {
|
||||
peer_mgr
|
||||
}
|
||||
|
||||
async fn create_mock_credential_peer_manager_for_foreign_network(
|
||||
network: &str,
|
||||
) -> Arc<PeerManager> {
|
||||
let (s, _r) = create_packet_recv_chan();
|
||||
let global_ctx = get_mock_global_ctx_with_network(Some(NetworkIdentity::new_credential(
|
||||
network.to_string(),
|
||||
)));
|
||||
set_secure_mode_cfg(&global_ctx, true);
|
||||
let peer_mgr = Arc::new(PeerManager::new(RouteAlgoType::Ospf, global_ctx, s));
|
||||
replace_stun_info_collector(peer_mgr.clone(), NatType::Unknown);
|
||||
peer_mgr.run().await.unwrap();
|
||||
peer_mgr
|
||||
}
|
||||
|
||||
pub async fn create_mock_peer_manager_for_foreign_network(network: &str) -> Arc<PeerManager> {
|
||||
create_mock_peer_manager_for_foreign_network_ext(network, network).await
|
||||
}
|
||||
@@ -1027,6 +1097,86 @@ pub mod tests {
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn credential_pubkey_trust_requires_ospf_credential_source() {
|
||||
let global_ctx = get_mock_global_ctx_with_network(Some(NetworkIdentity::new(
|
||||
"__access__".to_string(),
|
||||
"access_secret".to_string(),
|
||||
)));
|
||||
let foreign_network = NetworkIdentity::new("net1".to_string(), "net1_secret".to_string());
|
||||
let (pm_packet_sender, _pm_packet_recv) = create_packet_recv_chan();
|
||||
let entry = ForeignNetworkEntry::new(
|
||||
foreign_network.clone(),
|
||||
1,
|
||||
global_ctx.clone(),
|
||||
false,
|
||||
Arc::new(PeerSessionStore::new()),
|
||||
pm_packet_sender,
|
||||
);
|
||||
let pubkey = vec![7; 32];
|
||||
|
||||
entry.global_ctx.update_trusted_keys(
|
||||
HashMap::from([(
|
||||
pubkey.clone(),
|
||||
crate::common::global_ctx::TrustedKeyMetadata {
|
||||
source: TrustedKeySource::OspfNode,
|
||||
expiry_unix: None,
|
||||
},
|
||||
)]),
|
||||
&foreign_network.network_name,
|
||||
);
|
||||
assert!(!ForeignNetworkManager::is_credential_pubkey_trusted(&entry, &pubkey).await);
|
||||
|
||||
entry.global_ctx.update_trusted_keys(
|
||||
HashMap::from([(
|
||||
pubkey.clone(),
|
||||
crate::common::global_ctx::TrustedKeyMetadata {
|
||||
source: TrustedKeySource::OspfCredential,
|
||||
expiry_unix: None,
|
||||
},
|
||||
)]),
|
||||
&foreign_network.network_name,
|
||||
);
|
||||
assert!(ForeignNetworkManager::is_credential_pubkey_trusted(&entry, &pubkey).await);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn credential_trust_path_rejects_admin_identity() {
|
||||
assert!(ForeignNetworkManager::should_reject_credential_trust_path(
|
||||
PeerIdentityType::Admin
|
||||
));
|
||||
assert!(!ForeignNetworkManager::should_reject_credential_trust_path(
|
||||
PeerIdentityType::Credential
|
||||
));
|
||||
assert!(!ForeignNetworkManager::should_reject_credential_trust_path(
|
||||
PeerIdentityType::SharedNode
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn zero_digest_peer_cannot_bootstrap_foreign_network() {
|
||||
let pm_center = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
|
||||
set_secure_mode_cfg(&pm_center.get_global_ctx(), true);
|
||||
|
||||
let pma_net1 = create_mock_credential_peer_manager_for_foreign_network("net1").await;
|
||||
|
||||
let (a_ring, b_ring) = crate::tunnel::ring::create_ring_tunnel_pair();
|
||||
let a_mgr_copy = pma_net1.clone();
|
||||
let client = tokio::spawn(async move { a_mgr_copy.add_client_tunnel(a_ring, false).await });
|
||||
let b_mgr_copy = pm_center.clone();
|
||||
let server =
|
||||
tokio::spawn(async move { b_mgr_copy.add_tunnel_as_server(b_ring, true).await });
|
||||
|
||||
assert!(client.await.unwrap().is_ok());
|
||||
assert!(server.await.unwrap().is_err());
|
||||
assert!(pm_center
|
||||
.get_foreign_network_manager()
|
||||
.list_foreign_networks()
|
||||
.await
|
||||
.foreign_networks
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
async fn foreign_network_whitelist_helper(name: String) {
|
||||
let pm_center = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
|
||||
tracing::debug!("pm_center: {:?}", pm_center.my_peer_id());
|
||||
|
||||
Reference in New Issue
Block a user