Files
tsunagi/tests/network_isolation.rs
T

267 lines
8.6 KiB
Rust
Raw Normal View History

//! Scenario 4: one agent in two networks at once, with no bleed between them.
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod common;
use common::{TestAgent, network, settle, wait_event, wait_for_peers};
use iroh::endpoint::{PortmapperConfig, presets};
use iroh::{Endpoint, RelayMode};
use tsunagi::agent::Event;
use tsunagi::discovery::SharedMemoryDiscovery;
use tsunagi::identity::NetworkKeys;
use tsunagi::proto::handshake::ROLE_INITIATOR;
use tsunagi::proto::message::{
ALPN, AuthProof, ControlMessage, Envelope, Hello, HelloAck, PROTOCOL_VERSION, decode, encode,
};
use tsunagi::proto::{read_frame, write_frame};
use tsunagi::test_support;
const LIMIT: usize = 64 * 1024;
#[tokio::test]
async fn one_agent_in_two_networks_keeps_them_apart() {
let discovery = SharedMemoryDiscovery::new();
let (name_a, secret_a) = network("alpha-net");
let (name_b, secret_b) = network("beta-net");
let hub = TestAgent::spawn(&discovery).await.unwrap();
let alpha_peer = TestAgent::spawn(&discovery).await.unwrap();
let beta_peer = TestAgent::spawn(&discovery).await.unwrap();
let alpha = hub.agent.join_network(&name_a, &secret_a).await.unwrap();
let beta = hub.agent.join_network(&name_b, &secret_b).await.unwrap();
assert_ne!(alpha, beta);
alpha_peer
.agent
.join_network(&name_a, &secret_a)
.await
.unwrap();
beta_peer
.agent
.join_network(&name_b, &secret_b)
.await
.unwrap();
wait_for_peers(&hub.agent, alpha, 1).await;
wait_for_peers(&hub.agent, beta, 1).await;
// Statuses do not mix: each network sees exactly its own peer.
let status_alpha = hub.agent.network_status(alpha).await.unwrap();
let status_beta = hub.agent.network_status(beta).await.unwrap();
assert_eq!(
status_alpha.connected_peers(),
vec![alpha_peer.agent.endpoint_id()]
);
assert_eq!(
status_beta.connected_peers(),
vec![beta_peer.agent.endpoint_id()]
);
// Addressing a peer of one network through the other is refused locally.
let wrong = hub
.agent
.send(
alpha,
beta_peer.agent.endpoint_id(),
ControlMessage::Ping {
seq: 1,
payload: Vec::new(),
},
)
.await;
assert!(matches!(wrong, Err(tsunagi::Error::NoSuchPeer { .. })));
// Messages stay in their own network.
let mut events = hub.agent.subscribe();
hub.agent
.broadcast(
alpha,
ControlMessage::Ping {
seq: 7,
payload: b"alpha-only".to_vec(),
},
)
.await
.unwrap();
let from = wait_event(&mut events, |event| match event {
Event::MessageReceived {
network,
peer,
message: ControlMessage::Pong { seq: 7, payload },
} if payload == b"alpha-only" => Some((*network, *peer)),
_ => None,
})
.await;
assert_eq!(from, (alpha, alpha_peer.agent.endpoint_id()));
let beta_status = hub.agent.network_status(beta).await.unwrap();
assert_eq!(
beta_status.metrics.control_messages_received,
beta_status.peers[0].control_messages_received,
"beta's counters are its own"
);
assert!(
beta_status
.peers
.iter()
.all(|peer| peer.endpoint_id != alpha_peer.agent.endpoint_id())
);
// Deactivating one network must not disturb the other.
hub.agent.deactivate_network(alpha).await.unwrap();
assert!(hub.agent.network_status(alpha).await.is_err());
wait_for_peers(&hub.agent, beta, 1).await;
hub.agent
.send(
beta,
beta_peer.agent.endpoint_id(),
ControlMessage::Ping {
seq: 8,
payload: b"still-here".to_vec(),
},
)
.await
.unwrap();
wait_event(&mut events, |event| match event {
Event::MessageReceived {
network,
message: ControlMessage::Pong { seq: 8, .. },
..
} if *network == beta => Some(()),
_ => None,
})
.await;
hub.agent.shutdown().await;
alpha_peer.agent.shutdown().await;
beta_peer.agent.shutdown().await;
}
#[tokio::test]
async fn an_authenticated_session_cannot_speak_for_another_network() {
let discovery = SharedMemoryDiscovery::new();
let (name_a, secret_a) = network("session-scope-a");
let (name_b, secret_b) = network("session-scope-b");
let hub = TestAgent::spawn(&discovery).await.unwrap();
let mut events = hub.agent.subscribe();
let alpha = hub.agent.join_network(&name_a, &secret_a).await.unwrap();
let beta = hub.agent.join_network(&name_b, &secret_b).await.unwrap();
// A genuine member of `alpha`, driven by hand so it can misbehave.
let keys = NetworkKeys::derive(&name_a, &secret_a);
let auth_key = test_support::auth_key(&keys);
let member = Endpoint::builder(presets::Minimal)
.alpns(vec![ALPN.to_vec()])
.relay_mode(RelayMode::Disabled)
.clear_address_lookup()
.portmapper_config(PortmapperConfig::Disabled)
.clear_ip_transports()
.bind_addr("127.0.0.1:0")
.unwrap()
.bind()
.await
.unwrap();
let conn = member.connect(hub.agent.local_addr(), ALPN).await.unwrap();
let (mut send, mut recv) = conn.open_bi().await.unwrap();
let alpha_bytes = *alpha.as_bytes();
let nonce_i = [5u8; 16];
let hello = Hello {
version: PROTOCOL_VERSION,
network_id: alpha_bytes,
nonce: nonce_i,
};
write_frame(&mut send, &encode(&hello).unwrap(), LIMIT)
.await
.unwrap();
let ack: HelloAck = decode(&read_frame(&mut recv, LIMIT).await.unwrap()).unwrap();
let cb = test_support::channel_binding(&conn, &alpha_bytes).unwrap();
let proof = test_support::compute_proof(
&auth_key,
ROLE_INITIATOR,
PROTOCOL_VERSION,
&alpha_bytes,
member.id().as_bytes(),
hub.agent.endpoint_id().as_bytes(),
&cb,
&nonce_i,
&ack.nonce,
);
write_frame(&mut send, &encode(&AuthProof { proof }).unwrap(), LIMIT)
.await
.unwrap();
let _responder_proof: AuthProof = decode(&read_frame(&mut recv, LIMIT).await.unwrap()).unwrap();
// Authenticated for alpha. Now try to speak for beta on the same session.
let smuggled = Envelope {
network_id: *beta.as_bytes(),
message: ControlMessage::Ping {
seq: 99,
payload: b"wrong network".to_vec(),
},
};
write_frame(&mut send, &encode(&smuggled).unwrap(), LIMIT)
.await
.unwrap();
let reason = wait_event(&mut events, |event| match event {
Event::ProtocolViolation {
network, reason, ..
} if *network == Some(alpha) => Some(reason.clone()),
_ => None,
})
.await;
assert!(
reason.contains("network id does not match"),
"unexpected reason: {reason}"
);
// Beta saw nothing at all, and both networks keep running.
let beta_status = hub.agent.network_status(beta).await.unwrap();
assert_eq!(beta_status.metrics.control_messages_received, 0);
assert!(beta_status.peers.is_empty());
assert!(hub.agent.network_status(alpha).await.is_ok());
conn.close(0u32.into(), b"done");
member.close().await;
hub.agent.shutdown().await;
}
#[tokio::test]
async fn deactivating_one_network_leaves_the_agent_and_others_running() {
let discovery = SharedMemoryDiscovery::new();
let (name_a, secret_a) = network("keep-a");
let (name_b, secret_b) = network("keep-b");
let agent = TestAgent::spawn(&discovery).await.unwrap();
let alpha = agent.agent.join_network(&name_a, &secret_a).await.unwrap();
let beta = agent.agent.join_network(&name_b, &secret_b).await.unwrap();
agent.agent.deactivate_network(alpha).await.unwrap();
settle().await;
let status = agent.agent.status().await.unwrap();
assert_eq!(status.networks.len(), 2, "both stay configured");
assert_eq!(
status.network(&alpha).map(|net| net.state),
Some(tsunagi::agent::NetworkState::Inactive)
);
assert_eq!(
status.network(&beta).map(|net| net.state),
Some(tsunagi::agent::NetworkState::Active)
);
// Deactivating twice is an error, not a crash.
assert!(agent.agent.deactivate_network(alpha).await.is_err());
// And it can be brought back.
agent.agent.activate_network(alpha).await.unwrap();
assert!(agent.agent.network_status(alpha).await.is_ok());
agent.agent.shutdown().await;
}