use std::time::Duration;
use iroh::endpoint::{AfterHandshakeOutcome, Connection, EndpointHooks};
use tokio::sync::mpsc;
use super::*;
use crate::commands::dkg::ceremony::peer::ConnectedPeer;
use crate::commands::dkg::tests::relay::LocalRelay;
#[tokio::test]
async fn relay_enabled_endpoints_authenticate_using_discovered_addresses() -> TestResult {
let relay = LocalRelay::start().await?;
let secret_a = IrohSecretKey::generate();
let secret_b = IrohSecretKey::generate();
let key_a = SigningKey::new();
let key_b = SigningKey::new();
let mut ceremony_a =
test_ceremony(&key_a, secret_a.clone(), vec![(secret_b.public(), key_b.public_key())]);
let mut ceremony_b =
test_ceremony(&key_b, secret_b, vec![(secret_a.public(), key_a.public_key())]);
ceremony_a.enable_public_relay = true;
ceremony_b.enable_public_relay = true;
ceremony_a.bind_address = None;
ceremony_b.bind_address = None;
let endpoint_a = ceremony_a.endpoint_builder(&relay)?.bind().await?;
let endpoint_b = ceremony_b.endpoint_builder(&relay)?.bind().await?;
for endpoint in [&endpoint_a, &endpoint_b] {
relay.lookup.add_endpoint_info(
iroh::EndpointAddr::new(endpoint.id()).with_relay_url(relay.url.clone()),
);
}
let result = tokio::time::timeout(Duration::from_secs(10), async {
tokio::try_join!(
ceremony_a.authenticate_peers(&endpoint_a),
ceremony_b.authenticate_peers(&endpoint_b),
)
})
.await;
endpoint_a.close().await;
endpoint_b.close().await;
let (peers_a, peers_b) = result??;
assert_eq!(peers_a.authenticated_peers[0].validator_public_key(), &key_b.public_key());
assert_eq!(peers_b.authenticated_peers[0].validator_public_key(), &key_a.public_key());
Ok(())
}
#[rstest::rstest]
#[case::failed_connection(b"unsupported-protocol")]
#[case::unconfigured_endpoint(Ceremony::ALPN)]
#[tokio::test]
async fn unrelated_connection_does_not_abort_authentication(#[case] alpn: &[u8]) -> TestResult {
let mut secrets = [IrohSecretKey::generate(), IrohSecretKey::generate()];
secrets.sort_by_key(IrohSecretKey::public);
let [peer_secret, local_secret] = secrets;
let (endpoint, _) = bind_test_endpoint(local_secret.clone()).await?;
let (peer, peer_lookup) = bind_test_endpoint(peer_secret).await?;
peer_lookup.add_endpoint_info(endpoint.addr());
let (unrelated, _) = bind_test_endpoint(IrohSecretKey::generate()).await?;
let local_signing_key = SigningKey::new();
let peer_signing_key = SigningKey::new();
let ceremony = test_ceremony(
&local_signing_key,
local_secret,
vec![(peer.id(), peer_signing_key.public_key())],
);
let signer = ValidatorSigner::new_local(peer_signing_key);
let (peers, _remote_peer) = tokio::time::timeout(Duration::from_secs(5), async {
tokio::try_join!(ceremony.authenticate_peers(&endpoint), async {
let connection = unrelated.connect(endpoint.addr(), alpn).await;
if alpn == Ceremony::ALPN {
if let Ok(connection) = connection {
connection.closed().await;
}
} else {
assert!(connection.is_err(), "the unsupported protocol must fail establishment");
}
ConnectedPeer::connect(&peer, endpoint.id().into())
.await?
.authenticate(&local_signing_key.public_key(), &signer)
.await
})
})
.await??;
assert_eq!(peers.authenticated_peers.len(), 1);
assert_eq!(peers.authenticated_peers[0].validator_public_key(), &signer.public_key());
endpoint.close().await;
peer.close().await;
unrelated.close().await;
Ok(())
}
#[derive(Debug)]
struct StallConnection {
endpoint: EndpointId,
started: mpsc::UnboundedSender<()>,
}
impl EndpointHooks for StallConnection {
async fn after_handshake(&self, connection: &Connection) -> AfterHandshakeOutcome {
if connection.remote_id() == self.endpoint {
self.started.send(()).expect("test must keep the notification receiver open");
std::future::pending().await
} else {
AfterHandshakeOutcome::Accept
}
}
}
#[rstest::rstest]
#[case::one_stalled_connection(1)]
#[case::full_capacity(Ceremony::MAX_PENDING_CONNECTIONS)]
#[tokio::test]
async fn stalled_connections_do_not_prevent_peer_authentication(
#[case] stalled_count: usize,
) -> TestResult {
let mut secrets = [IrohSecretKey::generate(), IrohSecretKey::generate()];
secrets.sort_by_key(IrohSecretKey::public);
let [peer_secret, local_secret] = secrets;
let (peer, peer_lookup) = bind_test_endpoint(peer_secret).await?;
let (unrelated, _) = bind_test_endpoint(IrohSecretKey::generate()).await?;
let (started, mut stalled) = mpsc::unbounded_channel();
let endpoint = Endpoint::builder(presets::Minimal)
.secret_key(local_secret.clone())
.alpns(vec![Ceremony::ALPN.to_vec()])
.clear_ip_transports()
.bind_addr("127.0.0.1:0")?
.hooks(StallConnection { endpoint: unrelated.id(), started })
.bind()
.await?;
peer_lookup.add_endpoint_info(endpoint.addr());
let local_signing_key = SigningKey::new();
let peer_signing_key = SigningKey::new();
let ceremony = test_ceremony(
&local_signing_key,
local_secret,
vec![(peer.id(), peer_signing_key.public_key())],
);
let signer = ValidatorSigner::new_local(peer_signing_key);
let (peers, _remote_peer) = tokio::time::timeout(Duration::from_secs(20), async {
tokio::try_join!(ceremony.authenticate_peers(&endpoint), async {
let mut connections = Vec::new();
for _ in 0..stalled_count {
connections.push(unrelated.connect(endpoint.addr(), Ceremony::ALPN).await?);
stalled.recv().await.expect("connection must reach the establishment hook");
}
if stalled_count == Ceremony::MAX_PENDING_CONNECTIONS {
let result = unrelated.connect(endpoint.addr(), Ceremony::ALPN).await;
assert!(result.is_err(), "excess connections must be refused");
for connection in &connections {
connection.closed().await;
}
}
let authenticated = tokio::time::timeout(Duration::from_secs(5), async {
ConnectedPeer::connect(&peer, endpoint.id().into())
.await?
.authenticate(&local_signing_key.public_key(), &signer)
.await
})
.await??;
Ok::<_, anyhow::Error>(authenticated)
})
})
.await??;
assert_eq!(peers.authenticated_peers.len(), 1);
assert_eq!(peers.authenticated_peers[0].validator_public_key(), &signer.public_key());
endpoint.close().await;
peer.close().await;
unrelated.close().await;
Ok(())
}
#[rstest::rstest]
#[case::duplicate_incoming(true)]
#[case::wrong_direction(false)]
#[tokio::test]
async fn extra_connection_does_not_replace_an_authenticated_peer(
#[case] remote_is_dialer: bool,
) -> TestResult {
let mut secrets =
[IrohSecretKey::generate(), IrohSecretKey::generate(), IrohSecretKey::generate()];
secrets.sort_by_key(IrohSecretKey::public);
let [missing_secret, lower_secret, higher_secret] = secrets;
let (local_secret, peer_secret) = if remote_is_dialer {
(higher_secret, lower_secret)
} else {
(lower_secret, higher_secret)
};
let (endpoint, lookup) = bind_test_endpoint(local_secret.clone()).await?;
let (peer, peer_lookup) = bind_test_endpoint(peer_secret).await?;
let (missing, missing_lookup) = bind_test_endpoint(missing_secret).await?;
lookup.add_endpoint_info(peer.addr());
peer_lookup.add_endpoint_info(endpoint.addr());
missing_lookup.add_endpoint_info(endpoint.addr());
let local_signing_key = SigningKey::new();
let peer_signing_key = SigningKey::new();
let missing_signing_key = SigningKey::new();
let ceremony = test_ceremony(
&local_signing_key,
local_secret,
vec![
(peer.id(), peer_signing_key.public_key()),
(missing.id(), missing_signing_key.public_key()),
],
);
let peer_signer = ValidatorSigner::new_local(peer_signing_key);
let missing_signer = ValidatorSigner::new_local(missing_signing_key);
let (peers, remote_peers) = tokio::time::timeout(Duration::from_secs(5), async {
tokio::try_join!(ceremony.authenticate_peers(&endpoint), async {
let connection = if remote_is_dialer {
ConnectedPeer::connect(&peer, endpoint.id().into()).await?
} else {
let incoming = peer.accept().await.expect("test endpoint must stay open");
ConnectedPeer::accept(incoming).await?
};
let authenticated =
connection.authenticate(&local_signing_key.public_key(), &peer_signer).await?;
if let Ok(extra) = peer.connect(endpoint.addr(), Ceremony::ALPN).await {
extra.closed().await;
}
let last = ConnectedPeer::connect(&missing, endpoint.id().into())
.await?
.authenticate(&local_signing_key.public_key(), &missing_signer)
.await?;
Ok::<_, anyhow::Error>([authenticated, last])
})
})
.await??;
assert_eq!(peers.authenticated_peers.len(), 2);
assert!(remote_peers[0].connection().close_reason().is_none());
endpoint.close().await;
peer.close().await;
missing.close().await;
Ok(())
}