Files
Easytier/easytier-core/src/peers/tests.rs
T
KKRainbow 636390ec38 feat(peer): echo liveness probes on data traffic (#2497)
* 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.
2026-08-15 00:21:07 +08:00

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)
);
}