Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion iroh-relay/src/relay_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
/// ```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc comment should probably point out that a default RelayConfig is used and that this means we'll assume the relay URL has QAD running on port 7842 or whatever relevant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

pub fn try_from_iter<'a, T: IntoIterator<Item = &'a str>>(
urls: T,
) -> Result<Self, RelayUrlParseError> {
let relays: BTreeMap<RelayUrl, Arc<RelayConfig>> = urls
.into_iter()
.map(|t| {
t.parse()
.map(|url: RelayUrl| (url.clone(), Arc::new(RelayConfig::from(url))))
})
.collect::<Result<_, _>>()?;
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
Expand Down Expand Up @@ -214,6 +240,8 @@ fn quic_config() -> Option<RelayQuicConfig> {

/// 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.
Expand Down
44 changes: 42 additions & 2 deletions iroh/src/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = RelayUrl>) -> Self {
let m = RelayMap::from_iter(map);
Self::Custom(m)
}
}

/// Environment variable to force the use of staging relays.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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/",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are still inconsistent with our usage of . and / for the relay urls everywhere.

We should be sticking to .

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should stick to . in all examples.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And of course, defaults.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not an example but a test, I a actually want to make sure that it also works without . tbh

Copy link
Collaborator

@Arqu Arqu Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, wasn't super clear from that. I thought you wanted to do the same thing in 2 ways ie empty builder and pass it in from an iter.

])?;
let _ep = Endpoint::empty_builder(RelayMode::Custom(relays))
.bind()
.await?;

Ok(())
}
}
Loading