diff --git a/iroh-relay/src/relay_map.rs b/iroh-relay/src/relay_map.rs index 355c3dc8f2..48c0cec8f5 100644 --- a/iroh-relay/src/relay_map.rs +++ b/iroh-relay/src/relay_map.rs @@ -6,7 +6,7 @@ use std::{ sync::{Arc, RwLock}, }; -use iroh_base::RelayUrl; +use iroh_base::{RelayUrl, RelayUrlParseError}; use serde::{Deserialize, Serialize}; use crate::defaults::DEFAULT_RELAY_QUIC_PORT; @@ -50,6 +50,32 @@ impl RelayMap { } } + /// Creates a [`RelayMap`] from an iterator. + /// + /// The conversion from a URL to a [`RelayConfig`] is done the same as when parsing it directly, + /// which means it is assumed to run QUIC on default settings as defined in [`RelayQuicConfig::default`]. + /// + /// # Example + /// ```rust + /// # use iroh_relay::RelayMap; + /// let map = + /// RelayMap::try_from_iter(["https://relay_0.cool.com", "https://relay_1.cool.com"]).unwrap(); + /// ``` + pub fn try_from_iter<'a, T: IntoIterator>( + urls: T, + ) -> Result { + let relays: BTreeMap> = urls + .into_iter() + .map(|t| { + t.parse() + .map(|url: RelayUrl| (url.clone(), Arc::new(RelayConfig::from(url)))) + }) + .collect::>()?; + Ok(Self { + relays: Arc::new(RwLock::new(relays)), + }) + } + /// Returns the URLs of all servers in this relay map. /// /// This function is generic over the container to collect into. If you simply want a list @@ -214,6 +240,8 @@ fn quic_config() -> Option { /// Configuration for speaking to the QUIC endpoint on the relay /// server to do QUIC address discovery. +/// +/// Defaults to using [`DEFAULT_RELAY_QUIC_PORT`]. #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, PartialOrd, Ord)] pub struct RelayQuicConfig { /// The port on which the connection should be bound to. diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index d6ef7950b1..8ee64b589d 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -1260,6 +1260,24 @@ impl RelayMode { RelayMode::Custom(relay_map) => relay_map.clone(), } } + + /// Create a custom relay mode from a list of [`RelayUrl`]s. + /// + /// # Example + /// + /// ``` + /// # fn main() -> n0_error::Result<()> { + /// # use iroh::RelayMode; + /// RelayMode::custom([ + /// "https://use1-1.relay.n0.iroh-canary.iroh.link.".parse()?, + /// "https://euw-1.relay.n0.iroh-canary.iroh.link.".parse()?, + /// ]); + /// # Ok(()) } + /// ``` + pub fn custom(map: impl IntoIterator) -> Self { + let m = RelayMap::from_iter(map); + Self::Custom(m) + } } /// Environment variable to force the use of staging relays. @@ -1294,9 +1312,12 @@ fn is_cgi() -> bool { // https://github.com/n0-computer/iroh/issues/1183 #[cfg(test)] mod tests { - use std::time::{Duration, Instant}; + use std::{ + str::FromStr, + time::{Duration, Instant}, + }; - use iroh_base::{EndpointAddr, EndpointId, SecretKey, TransportAddr}; + use iroh_base::{EndpointAddr, EndpointId, RelayUrl, SecretKey, TransportAddr}; use n0_error::{AnyError as Error, Result, StdResultExt}; use n0_future::{BufferedStreamExt, StreamExt, stream, time}; use n0_watcher::Watcher; @@ -2450,4 +2471,23 @@ mod tests { assert!(dt0 / dt1 < 20.0, "First round: {dt0}s, second round {dt1}s"); Ok(()) } + + #[tokio::test] + async fn test_custom_relay() -> Result { + let _ep = Endpoint::empty_builder(RelayMode::custom([RelayUrl::from_str( + "https://use1-1.relay.n0.iroh-canary.iroh.link.", + )?])) + .bind() + .await?; + + let relays = RelayMap::try_from_iter([ + "https://use1-1.relay.n0.iroh.iroh.link/", + "https://euc1-1.relay.n0.iroh.iroh.link/", + ])?; + let _ep = Endpoint::empty_builder(RelayMode::Custom(relays)) + .bind() + .await?; + + Ok(()) + } }