mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-01 00:39:24 +00:00
636390ec38
* feat(peer): echo liveness probes on data traffic Advertise a liveness-echo capability during classic and Noise handshakes. After a ping failure, tag outgoing peer packets with a short probe token and accept only the matching echoed token as round-trip proof. Keep one ping request outstanding and coalesce scheduler triggers so high traffic cannot reorder timeout results. Preserve one-way failure detection because unrelated ingress never clears the loss counter. * test(three_node): relax disconnect wait for sequential pingpong proxy_three_node_disconnect_test assumed the old pingpong timing, where overlapping pings failed fast and the connection closed well inside the 11s wait (see the old [4, 9)s comment). The liveness-echo change keeps one ping outstanding: each failure now takes a full 2s timeout, so the fifth consecutive failure and the connection close land at ~11s. Both proto variants timed out at the 11s bound in CI. Widen the wait to 15s and update the timing comment.
254 lines
8.7 KiB
Rust
254 lines
8.7 KiB
Rust
use std::sync::Arc;
|
|
|
|
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD};
|
|
use x25519_dalek::{PublicKey, StaticSecret};
|
|
|
|
use crate::foundation::time::{Duration, timeout};
|
|
|
|
use crate::{
|
|
packet::{PacketType, ZCPacket},
|
|
peers::{
|
|
conn::{
|
|
peer_conn::{PeerConn, PeerConnId},
|
|
peer_map::PeerMap,
|
|
peer_session::PeerSessionStore,
|
|
},
|
|
context::NetworkIdentity,
|
|
create_packet_recv_chan,
|
|
error::Error,
|
|
test_support::NoopPeerContext,
|
|
},
|
|
tunnel::ring::create_ring_tunnel_pair,
|
|
};
|
|
|
|
impl PeerConn {
|
|
#[tracing::instrument]
|
|
async fn do_handshake_as_server(&mut self) -> Result<(), Error> {
|
|
self.do_handshake_as_server_ext(|_, _| Ok(())).await
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn peer_conn_handshake_over_memory_tunnel() {
|
|
let peer_session_store = Arc::new(PeerSessionStore::new());
|
|
let (client_tunnel, server_tunnel) = create_ring_tunnel_pair();
|
|
let client_ctx = Arc::new(NoopPeerContext::default());
|
|
let server_ctx = Arc::new(NoopPeerContext::default());
|
|
|
|
let mut client = PeerConn::new(1, client_ctx, client_tunnel, peer_session_store.clone());
|
|
let mut server = PeerConn::new(2, server_ctx, server_tunnel, peer_session_store);
|
|
|
|
let (client_ret, server_ret) = tokio::join!(
|
|
client.do_handshake_as_client(),
|
|
server.do_handshake_as_server()
|
|
);
|
|
|
|
client_ret.unwrap();
|
|
server_ret.unwrap();
|
|
assert_eq!(client.get_peer_id(), 2);
|
|
assert_eq!(server.get_peer_id(), 1);
|
|
assert_eq!(client.get_conn_info().features, ["liveness-echo-v1"]);
|
|
assert_eq!(server.get_conn_info().features, ["liveness-echo-v1"]);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn peer_conn_noise_handshake_advertises_liveness_echo() {
|
|
fn context(peer_key: u8) -> Arc<NoopPeerContext> {
|
|
let private = StaticSecret::from([peer_key; 32]);
|
|
let public = PublicKey::from(&private);
|
|
Arc::new(
|
|
NoopPeerContext::new(NetworkIdentity {
|
|
network_name: "net".to_owned(),
|
|
network_secret: Some("secret".to_owned()),
|
|
network_secret_digest: None,
|
|
})
|
|
.with_secure_mode(crate::proto::common::SecureModeConfig {
|
|
enabled: true,
|
|
local_private_key: Some(BASE64_STANDARD.encode(private.as_bytes())),
|
|
local_public_key: Some(BASE64_STANDARD.encode(public.as_bytes())),
|
|
}),
|
|
)
|
|
}
|
|
|
|
let (client_tunnel, server_tunnel) = create_ring_tunnel_pair();
|
|
let mut client = PeerConn::new(
|
|
1,
|
|
context(1),
|
|
client_tunnel,
|
|
Arc::new(PeerSessionStore::new()),
|
|
);
|
|
let mut server = PeerConn::new(
|
|
2,
|
|
context(2),
|
|
server_tunnel,
|
|
Arc::new(PeerSessionStore::new()),
|
|
);
|
|
|
|
let (client_ret, server_ret) = tokio::join!(
|
|
client.do_handshake_as_client(),
|
|
server.do_handshake_as_server()
|
|
);
|
|
|
|
client_ret.unwrap();
|
|
server_ret.unwrap();
|
|
assert_eq!(client.get_conn_info().features, ["liveness-echo-v1"]);
|
|
assert_eq!(server.get_conn_info().features, ["liveness-echo-v1"]);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn peer_conn_handshake_matches_plaintext_secret_identity() {
|
|
let peer_session_store = Arc::new(PeerSessionStore::new());
|
|
let (client_tunnel, server_tunnel) = create_ring_tunnel_pair();
|
|
let client_ctx = Arc::new(NoopPeerContext::new(NetworkIdentity {
|
|
network_name: "net".to_string(),
|
|
network_secret: Some("secret".to_string()),
|
|
network_secret_digest: None,
|
|
}));
|
|
let server_ctx = Arc::new(NoopPeerContext::new(NetworkIdentity {
|
|
network_name: "net".to_string(),
|
|
network_secret: Some("secret".to_string()),
|
|
network_secret_digest: None,
|
|
}));
|
|
|
|
let mut client = PeerConn::new(1, client_ctx, client_tunnel, peer_session_store.clone());
|
|
let mut server = PeerConn::new(2, server_ctx, server_tunnel, peer_session_store);
|
|
|
|
let (client_ret, server_ret) = tokio::join!(
|
|
client.do_handshake_as_client(),
|
|
server.do_handshake_as_server()
|
|
);
|
|
|
|
client_ret.unwrap();
|
|
server_ret.unwrap();
|
|
assert!(client.matches_local_network_secret());
|
|
assert!(server.matches_local_network_secret());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn peer_map_forwards_packet_over_memory_tunnel() {
|
|
let peer_session_store = Arc::new(PeerSessionStore::new());
|
|
let (client_tunnel, server_tunnel) = create_ring_tunnel_pair();
|
|
let client_ctx = Arc::new(NoopPeerContext::default());
|
|
let server_ctx = Arc::new(NoopPeerContext::default());
|
|
|
|
let mut client_conn = PeerConn::new(
|
|
1,
|
|
client_ctx.clone(),
|
|
client_tunnel,
|
|
peer_session_store.clone(),
|
|
);
|
|
let mut server_conn = PeerConn::new(2, server_ctx.clone(), server_tunnel, peer_session_store);
|
|
|
|
let (client_ret, server_ret) = tokio::join!(
|
|
client_conn.do_handshake_as_client(),
|
|
server_conn.do_handshake_as_server()
|
|
);
|
|
client_ret.unwrap();
|
|
server_ret.unwrap();
|
|
|
|
let (client_tx, _client_rx) = create_packet_recv_chan();
|
|
let (server_tx, mut server_rx) = create_packet_recv_chan();
|
|
let client_map = PeerMap::new(client_tx, client_ctx, 1);
|
|
let server_map = PeerMap::new(server_tx, server_ctx, 2);
|
|
|
|
client_map.add_new_peer_conn(client_conn).await.unwrap();
|
|
server_map.add_new_peer_conn(server_conn).await.unwrap();
|
|
|
|
let mut packet = ZCPacket::new_with_payload(b"hello");
|
|
packet.fill_peer_manager_hdr(1, 2, PacketType::Data as u8);
|
|
client_map.send_msg_directly(packet, 2).await.unwrap();
|
|
|
|
let received = timeout(Duration::from_secs(1), server_rx.recv())
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(received.payload(), b"hello");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn peer_map_reselects_cached_connection_after_close() {
|
|
let peer_session_store = Arc::new(PeerSessionStore::new());
|
|
let (client_tunnel_a, server_tunnel_a) = create_ring_tunnel_pair();
|
|
let (client_tunnel_b, server_tunnel_b) = create_ring_tunnel_pair();
|
|
let client_ctx = Arc::new(NoopPeerContext::default());
|
|
let server_ctx = Arc::new(NoopPeerContext::default());
|
|
|
|
let mut client_conn_a = PeerConn::new(
|
|
1,
|
|
client_ctx.clone(),
|
|
client_tunnel_a,
|
|
peer_session_store.clone(),
|
|
);
|
|
let mut server_conn_a = PeerConn::new(
|
|
2,
|
|
server_ctx.clone(),
|
|
server_tunnel_a,
|
|
peer_session_store.clone(),
|
|
);
|
|
let mut client_conn_b = PeerConn::new(
|
|
1,
|
|
client_ctx.clone(),
|
|
client_tunnel_b,
|
|
peer_session_store.clone(),
|
|
);
|
|
let mut server_conn_b =
|
|
PeerConn::new(2, server_ctx.clone(), server_tunnel_b, peer_session_store);
|
|
|
|
let (client_a_ret, server_a_ret, client_b_ret, server_b_ret) = tokio::join!(
|
|
client_conn_a.do_handshake_as_client(),
|
|
server_conn_a.do_handshake_as_server(),
|
|
client_conn_b.do_handshake_as_client(),
|
|
server_conn_b.do_handshake_as_server(),
|
|
);
|
|
client_a_ret.unwrap();
|
|
server_a_ret.unwrap();
|
|
client_b_ret.unwrap();
|
|
server_b_ret.unwrap();
|
|
|
|
let (client_tx, _client_rx) = create_packet_recv_chan();
|
|
let (server_tx, mut server_rx) = create_packet_recv_chan();
|
|
let client_map = PeerMap::new(client_tx, client_ctx, 1);
|
|
let server_map = PeerMap::new(server_tx, server_ctx, 2);
|
|
|
|
client_map.add_new_peer_conn(client_conn_a).await.unwrap();
|
|
client_map.add_new_peer_conn(client_conn_b).await.unwrap();
|
|
server_map.add_new_peer_conn(server_conn_a).await.unwrap();
|
|
server_map.add_new_peer_conn(server_conn_b).await.unwrap();
|
|
|
|
let mut first_packet = ZCPacket::new_with_payload(b"first");
|
|
first_packet.fill_peer_manager_hdr(1, 2, PacketType::Data as u8);
|
|
client_map.send_msg_directly(first_packet, 2).await.unwrap();
|
|
let first_received = timeout(Duration::from_secs(1), server_rx.recv())
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(first_received.payload(), b"first");
|
|
|
|
let first_conn_id = client_map.get_peer_default_conn_id(2).await.unwrap();
|
|
assert_ne!(first_conn_id, PeerConnId::default());
|
|
client_map.close_peer_conn(2, &first_conn_id).await.unwrap();
|
|
timeout(Duration::from_secs(1), async {
|
|
while client_map.get_peer_default_conn_id(2).await == Some(first_conn_id) {
|
|
tokio::task::yield_now().await;
|
|
}
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
let mut second_packet = ZCPacket::new_with_payload(b"second");
|
|
second_packet.fill_peer_manager_hdr(1, 2, PacketType::Data as u8);
|
|
client_map
|
|
.send_msg_directly(second_packet, 2)
|
|
.await
|
|
.unwrap();
|
|
let second_received = timeout(Duration::from_secs(1), server_rx.recv())
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(second_received.payload(), b"second");
|
|
assert_ne!(
|
|
client_map.get_peer_default_conn_id(2).await,
|
|
Some(first_conn_id)
|
|
);
|
|
}
|