diff --git a/README.md b/README.md index bc6c86eed1c..8ce6be7eb2e 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Install it using `cargo add iroh`, then on the connecting side: ```rs const ALPN: &[u8] = b"iroh-example/echo/0"; -let endpoint = Endpoint::builder().discovery_n0().bind().await?; +let endpoint = Endpoint::bind().await?; // Open a connection to the accepting endpoint let conn = endpoint.connect(addr, ALPN).await?; @@ -85,7 +85,7 @@ endpoint.close().await; And on the accepting side: ```rs -let endpoint = Endpoint::builder().discovery_n0().bind().await?; +let endpoint = Endpoint::bind().await?; let router = Router::builder(endpoint) .accept(ALPN.to_vec(), Arc::new(Echo)) diff --git a/iroh/examples/echo-no-router.rs b/iroh/examples/echo-no-router.rs index 2387dddf44c..770ddce3f32 100644 --- a/iroh/examples/echo-no-router.rs +++ b/iroh/examples/echo-no-router.rs @@ -33,7 +33,7 @@ async fn main() -> Result<()> { } async fn connect_side(addr: EndpointAddr) -> Result<()> { - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::bind().await?; // Open a connection to the accepting endpoint let conn = endpoint.connect(addr, ALPN).await?; @@ -68,7 +68,6 @@ async fn connect_side(addr: EndpointAddr) -> Result<()> { async fn start_accept_side() -> Result { let endpoint = Endpoint::builder() - .discovery_n0() // The accept side needs to opt-in to the protocols it accepts, // as any connection attempts that can't be found with a matching ALPN // will be rejected. diff --git a/iroh/examples/echo.rs b/iroh/examples/echo.rs index 1a3d1563725..a378b6469a9 100644 --- a/iroh/examples/echo.rs +++ b/iroh/examples/echo.rs @@ -35,7 +35,7 @@ async fn main() -> Result<()> { } async fn connect_side(addr: EndpointAddr) -> Result<()> { - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::bind().await?; // Open a connection to the accepting endpoint let conn = endpoint.connect(addr, ALPN).await?; @@ -68,7 +68,7 @@ async fn connect_side(addr: EndpointAddr) -> Result<()> { } async fn start_accept_side() -> Result { - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::bind().await?; // Build our protocol handler and add our protocol, identified by its ALPN, and spawn the endpoint. let router = Router::builder(endpoint).accept(ALPN, Echo).spawn(); diff --git a/iroh/examples/locally-discovered-nodes.rs b/iroh/examples/locally-discovered-nodes.rs index b1ed93b46aa..08e6226d94b 100644 --- a/iroh/examples/locally-discovered-nodes.rs +++ b/iroh/examples/locally-discovered-nodes.rs @@ -19,7 +19,7 @@ async fn main() -> Result<()> { tracing_subscriber::fmt::init(); println!("Discovering Local Endpoints Example!"); - let ep = Endpoint::builder().bind().await?; + let ep = Endpoint::bind().await?; let endpoint_id = ep.id(); let mdns = MdnsDiscovery::builder().build(endpoint_id)?; @@ -68,7 +68,8 @@ async fn main() -> Result<()> { for _ in 0..endpoint_count { let ud = user_data.clone(); set.spawn(async move { - let ep = Endpoint::builder().discovery_local_network().bind().await?; + let ep = Endpoint::bind().await?; + ep.discovery().add(MdnsDiscovery::builder().build(ep.id())?); ep.set_user_data_for_discovery(Some(ud)); tokio::time::sleep(Duration::from_secs(3)).await; ep.close().await; diff --git a/iroh/examples/screening-connection.rs b/iroh/examples/screening-connection.rs index 33f745bfd26..d556649b16c 100644 --- a/iroh/examples/screening-connection.rs +++ b/iroh/examples/screening-connection.rs @@ -46,7 +46,7 @@ async fn main() -> Result<()> { } async fn connect_side(addr: &EndpointAddr) -> Result<()> { - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::bind().await?; // Open a connection to the accepting endpoint let conn = endpoint.connect(addr.clone(), ALPN).await?; @@ -79,7 +79,7 @@ async fn connect_side(addr: &EndpointAddr) -> Result<()> { } async fn start_accept_side() -> Result { - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::bind().await?; let echo = ScreenedEcho { conn_attempt_count: Arc::new(AtomicU64::new(0)), diff --git a/iroh/examples/search.rs b/iroh/examples/search.rs index c2c2193b39b..e7c2df5aa2a 100644 --- a/iroh/examples/search.rs +++ b/iroh/examples/search.rs @@ -75,7 +75,7 @@ async fn main() -> Result<()> { let args = Cli::parse(); // Build an endpoint - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::bind().await?; // Build our protocol handler. The `builder` exposes access to various subsystems in the // iroh endpoint. In our case, we need a blobs client and the endpoint. diff --git a/iroh/examples/transfer.rs b/iroh/examples/transfer.rs index f2462c586ac..c26d4c0d7fe 100644 --- a/iroh/examples/transfer.rs +++ b/iroh/examples/transfer.rs @@ -220,27 +220,14 @@ impl EndpointArgs { let url = self .pkarr_relay_url .unwrap_or_else(|| self.env.pkarr_relay_url()); - builder = builder.add_discovery(PkarrPublisher::builder(url)); + builder = builder.discovery(PkarrPublisher::builder(url)); } if !self.no_dns_resolve { let domain = self .dns_origin_domain .unwrap_or_else(|| self.env.dns_origin_domain()); - builder = builder.add_discovery(DnsDiscovery::builder(domain)); - } - - if self.mdns { - #[cfg(feature = "discovery-local-network")] - { - builder = builder.discovery_local_network(); - } - #[cfg(not(feature = "discovery-local-network"))] - { - snafu::whatever!( - "Must have the `test-utils` feature enabled when using the `--relay-only` flag" - ); - } + builder = builder.discovery(DnsDiscovery::builder(domain)); } if self.relay_only { @@ -270,6 +257,23 @@ impl EndpointArgs { let endpoint = builder.alpns(vec![TRANSFER_ALPN.to_vec()]).bind().await?; + if self.mdns { + #[cfg(feature = "discovery-local-network")] + { + use iroh::discovery::mdns::MdnsDiscovery; + + endpoint + .discovery() + .add(MdnsDiscovery::builder().build(endpoint.id())?); + } + #[cfg(not(feature = "discovery-local-network"))] + { + snafu::whatever!( + "Must have the `test-utils` feature enabled when using the `--relay-only` flag" + ); + } + } + let endpoint_id = endpoint.id(); println!("Our endpoint id:\n\t{endpoint_id}"); diff --git a/iroh/src/discovery.rs b/iroh/src/discovery.rs index a12b0966a32..1e31e3d1c53 100644 --- a/iroh/src/discovery.rs +++ b/iroh/src/discovery.rs @@ -17,7 +17,7 @@ //! The [`Discovery`] trait is used to define endpoint discovery. This allows multiple //! implementations to co-exist because there are many possible ways to implement this. //! Each [`Endpoint`] can use the discovery mechanisms most suitable to the application. -//! The [`Builder::add_discovery`] method is used to add a discovery mechanism to an +//! The [`Builder::discovery`] method is used to add a discovery mechanism to an //! [`Endpoint`]. //! //! Some generally useful discovery implementations are provided: @@ -39,18 +39,18 @@ //! - The [`DhtDiscovery`] also uses the [`pkarr`] system but can also publish and lookup //! records to/from the Mainline DHT. //! -//! To use multiple discovery systems simultaneously you can call [`Builder::add_discovery`]. +//! To use multiple discovery systems simultaneously you can call [`Builder::discovery`]. //! This will use [`ConcurrentDiscovery`] under the hood, which performs lookups to all //! discovery systems at the same time. //! -//! [`Builder::add_discovery`] takes any type that implements [`IntoDiscovery`]. You can +//! [`Builder::discovery`] takes any type that implements [`IntoDiscovery`]. You can //! implement that trait on a builder struct if your discovery service needs information -//! from the endpoint it is mounted on. During endpoint construction, your discovery service -//! is built by calling [`IntoDiscovery::into_discovery`], passing a [`DiscoveryContext`] to your -//! builder. The [`DiscoveryContext`] gives access to the endpoint's secret key and DNS resolver. +//! from the endpoint it is mounted on. After endpoint construction, your discovery service +//! is built by calling [`IntoDiscovery::into_discovery`], passing the finished [`Endpoint`] to your +//! builder. //! //! If your discovery service does not need any information from its endpoint, you can -//! pass the discovery service directly to [`Builder::add_discovery`]: All types that +//! pass the discovery service directly to [`Builder::discovery`]: All types that //! implement [`Discovery`] also have a blanket implementation of [`IntoDiscovery`]. //! //! # Examples @@ -62,12 +62,13 @@ //! use iroh::{ //! Endpoint, SecretKey, //! discovery::{dns::DnsDiscovery, pkarr::PkarrPublisher}, +//! endpoint::RelayMode, //! }; //! //! # async fn wrapper() -> n0_snafu::Result<()> { -//! let ep = Endpoint::builder() -//! .add_discovery(PkarrPublisher::n0_dns()) -//! .add_discovery(DnsDiscovery::n0_dns()) +//! let ep = Endpoint::empty_builder(RelayMode::Default) +//! .discovery(PkarrPublisher::n0_dns()) +//! .discovery(DnsDiscovery::n0_dns()) //! .bind() //! .await?; //! # Ok(()) @@ -81,14 +82,15 @@ //! # { //! # use iroh::{ //! # discovery::{dns::DnsDiscovery, pkarr::PkarrPublisher, mdns::MdnsDiscovery}, +//! # endpoint::RelayMode, //! # Endpoint, SecretKey, //! # }; //! # //! # async fn wrapper() -> n0_snafu::Result<()> { -//! let ep = Endpoint::builder() -//! .add_discovery(PkarrPublisher::n0_dns()) -//! .add_discovery(DnsDiscovery::n0_dns()) -//! .add_discovery(MdnsDiscovery::builder()) +//! let ep = Endpoint::empty_builder(RelayMode::Default) +//! .discovery(PkarrPublisher::n0_dns()) +//! .discovery(DnsDiscovery::n0_dns()) +//! .discovery(MdnsDiscovery::builder()) //! .bind() //! .await?; //! # Ok(()) @@ -98,7 +100,7 @@ //! //! [`EndpointAddr`]: iroh_base::EndpointAddr //! [`RelayUrl`]: crate::RelayUrl -//! [`Builder::add_discovery`]: crate::endpoint::Builder::add_discovery +//! [`Builder::discovery`]: crate::endpoint::Builder::discovery //! [`DnsDiscovery`]: dns::DnsDiscovery //! [Number 0]: https://n0.computer //! [`PkarrResolver`]: pkarr::PkarrResolver @@ -122,10 +124,8 @@ use snafu::{IntoError, Snafu, ensure}; use tokio::sync::oneshot; use tracing::{Instrument, debug, error_span, warn}; -#[cfg(not(wasm_browser))] -use crate::dns::DnsResolver; +use crate::Endpoint; pub use crate::endpoint_info::{EndpointData, EndpointInfo, ParseError, UserData}; -use crate::{Endpoint, SecretKey}; #[cfg(not(wasm_browser))] pub mod dns; @@ -138,35 +138,25 @@ pub mod static_provider; /// Trait for structs that can be converted into [`Discovery`]. /// /// This trait is implemented on builders for discovery services. Any type that implements this -/// trait can be added as a discovery service in [`Builder::add_discovery`]. +/// trait can be added as a discovery service in [`Builder::discovery`]. /// /// Any type that implements [`Discovery`] also implements [`IntoDiscovery`]. /// /// Iroh uses this trait to allow configuring the set of discovery services on the endpoint /// builder, while providing the discovery services access to information about the endpoint -/// creation via the [`DiscoveryContext`] parameter to [`IntoDiscovery::into_discovery`]. +/// to [`IntoDiscovery::into_discovery`]. /// -/// [`Builder::add_discovery`]: crate::endpoint::Builder::add_discovery +/// [`Builder::discovery`]: crate::endpoint::Builder::discovery pub trait IntoDiscovery: Send + Sync + std::fmt::Debug + 'static { /// Turns this discovery builder into a ready-to-use discovery service. /// - /// The [`DiscoveryContext`] contains information about the [`Endpoint`] onto which this - /// discovery service is being added. It can be used by discovery services that need - /// a DNS resolver, or the endpoint's secret key to sign messages. - /// /// If an error is returned, building the endpoint will fail with this error. - fn into_discovery( - self, - context: &DiscoveryContext, - ) -> Result; + fn into_discovery(self, endpoint: &Endpoint) -> Result; } /// Blanket no-op impl of `IntoDiscovery` for `T: Discovery`. impl IntoDiscovery for T { - fn into_discovery( - self, - _context: &DiscoveryContext, - ) -> Result { + fn into_discovery(self, _endpoint: &Endpoint) -> Result { Ok(self) } } @@ -176,46 +166,20 @@ pub(crate) trait DynIntoDiscovery: Send + Sync + std::fmt::Debug + 'static { /// See [`IntoDiscovery::into_discovery`] fn into_discovery( self: Box, - context: &DiscoveryContext, + endpoint: &Endpoint, ) -> Result, IntoDiscoveryError>; } impl DynIntoDiscovery for T { fn into_discovery( self: Box, - context: &DiscoveryContext, + endpoint: &Endpoint, ) -> Result, IntoDiscoveryError> { - let disco: Box = Box::new(IntoDiscovery::into_discovery(*self, context)?); + let disco: Box = Box::new(IntoDiscovery::into_discovery(*self, endpoint)?); Ok(disco) } } -/// Context about the [`Endpoint`] for discovery services. -#[derive(Debug)] -pub struct DiscoveryContext<'a> { - #[cfg(not(wasm_browser))] - pub(crate) dns_resolver: &'a DnsResolver, - pub(crate) secret_key: &'a SecretKey, -} - -impl DiscoveryContext<'_> { - /// Returns the [`EndpointId`] of the endpoint. - pub fn endpoint_id(&self) -> EndpointId { - self.secret_key.public() - } - - /// Returns the [`SecretKey`] of the endpoint. - pub fn secret_key(&self) -> &SecretKey { - self.secret_key - } - - /// Returns the [`DnsResolver`] used by the endpoint. - #[cfg(not(wasm_browser))] - pub fn dns_resolver(&self) -> &DnsResolver { - self.dns_resolver - } -} - /// IntoDiscovery errors #[common_fields({ backtrace: Option, @@ -482,7 +446,7 @@ impl ConcurrentDiscovery { self.services.read().expect("poisoned").is_empty() } - /// How many services are configured + /// Returns the number of services configured. pub fn len(&self) -> usize { self.services.read().expect("poisoned").len() } @@ -676,7 +640,7 @@ mod tests { use iroh_base::{EndpointAddr, SecretKey}; use n0_snafu::{Error, Result, ResultExt}; use quinn::{IdleTimeout, TransportConfig}; - use rand::{Rng, SeedableRng}; + use rand::{CryptoRng, Rng, SeedableRng}; use tokio_util::task::AbortOnDropHandle; use tracing_test::traced_test; @@ -797,16 +761,11 @@ mod tests { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); let disco_shared = TestDiscoveryShared::default(); - let (ep1, _guard1) = { - let secret = SecretKey::generate(&mut rng); - let disco = disco_shared.create_discovery(secret.public()); - new_endpoint(secret, disco).await - }; - let (ep2, _guard2) = { - let secret = SecretKey::generate(&mut rng); - let disco = disco_shared.create_discovery(secret.public()); - new_endpoint(secret, disco).await - }; + let (ep1, _guard1) = + new_endpoint(&mut rng, |ep| disco_shared.create_discovery(ep.id())).await; + + let (ep2, _guard2) = + new_endpoint(&mut rng, |ep| disco_shared.create_discovery(ep.id())).await; let ep1_addr = EndpointAddr::new(ep1.id()); let _conn = ep2.connect(ep1_addr, TEST_ALPN).await?; Ok(()) @@ -819,18 +778,15 @@ mod tests { async fn endpoint_discovery_simple_shared_with_arc() -> Result { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); let disco_shared = TestDiscoveryShared::default(); - let (ep1, _guard1) = { - let secret = SecretKey::generate(&mut rng); - let disco = disco_shared.create_discovery(secret.public()); - let disco = Arc::new(disco); - new_endpoint(secret, disco).await - }; - let (ep2, _guard2) = { - let secret = SecretKey::generate(&mut rng); - let disco = disco_shared.create_discovery(secret.public()); - let disco = Arc::new(disco); - new_endpoint(secret, disco).await - }; + let (ep1, _guard1) = new_endpoint(&mut rng, |ep| { + Arc::new(disco_shared.create_discovery(ep.id())) + }) + .await; + + let (ep2, _guard2) = new_endpoint(&mut rng, |ep| { + Arc::new(disco_shared.create_discovery(ep.id())) + }) + .await; let ep1_addr = EndpointAddr::new(ep1.id()); let _conn = ep2.connect(ep1_addr, TEST_ALPN).await?; Ok(()) @@ -839,25 +795,22 @@ mod tests { /// This test adds an empty discovery which provides no addresses. #[tokio::test] #[traced_test] - async fn endpoint_discovery_combined_with_empty() -> Result { + async fn endpoint_discovery_combined_with_empty_and_right() -> Result { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); let disco_shared = TestDiscoveryShared::default(); - let (ep1, _guard1) = { - let secret = SecretKey::generate(&mut rng); - let disco = disco_shared.create_discovery(secret.public()); - new_endpoint(secret, disco).await - }; - let (ep2, _guard2) = { - let secret = SecretKey::generate(&mut rng); + let (ep1, _guard1) = + new_endpoint(&mut rng, |ep| disco_shared.create_discovery(ep.id())).await; + let (ep2, _guard2) = new_endpoint_add(&mut rng, |ep| { let disco1 = EmptyDiscovery; - let disco2 = disco_shared.create_discovery(secret.public()); - let disco = ConcurrentDiscovery::empty(); - disco.add(disco1); - disco.add(disco2); - new_endpoint(secret, disco).await - }; + let disco2 = disco_shared.create_discovery(ep.id()); + ep.discovery().add(disco1); + ep.discovery().add(disco2); + }) + .await; + let ep1_addr = EndpointAddr::new(ep1.id()); + assert_eq!(ep2.discovery().len(), 2); let _conn = ep2 .connect(ep1_addr, TEST_ALPN) .await @@ -873,22 +826,20 @@ mod tests { async fn endpoint_discovery_combined_with_empty_and_wrong() -> Result { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); let disco_shared = TestDiscoveryShared::default(); - let (ep1, _guard1) = { - let secret = SecretKey::generate(&mut rng); - let disco = disco_shared.create_discovery(secret.public()); - new_endpoint(secret, disco).await - }; - let (ep2, _guard2) = { - let secret = SecretKey::generate(&mut rng); + let (ep1, _guard1) = + new_endpoint(&mut rng, |ep| disco_shared.create_discovery(ep.id())).await; + + let (ep2, _guard2) = new_endpoint(&mut rng, |ep| { let disco1 = EmptyDiscovery; - let disco2 = disco_shared.create_lying_discovery(secret.public()); - let disco3 = disco_shared.create_discovery(secret.public()); + let disco2 = disco_shared.create_lying_discovery(ep.id()); + let disco3 = disco_shared.create_discovery(ep.id()); let disco = ConcurrentDiscovery::empty(); disco.add(disco1); disco.add(disco2); disco.add(disco3); - new_endpoint(secret, disco).await - }; + disco + }) + .await; let _conn = ep2.connect(ep1.id(), TEST_ALPN).await?; Ok(()) @@ -901,17 +852,14 @@ mod tests { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); let disco_shared = TestDiscoveryShared::default(); - let (ep1, _guard1) = { - let secret = SecretKey::generate(&mut rng); - let disco = disco_shared.create_discovery(secret.public()); - new_endpoint(secret, disco).await - }; - let (ep2, _guard2) = { - let secret = SecretKey::generate(&mut rng); - let disco1 = disco_shared.create_lying_discovery(secret.public()); - let disco = ConcurrentDiscovery::from_services(vec![Box::new(disco1)]); - new_endpoint(secret, disco).await - }; + let (ep1, _guard1) = + new_endpoint(&mut rng, |ep| disco_shared.create_discovery(ep.id())).await; + + let (ep2, _guard2) = new_endpoint(&mut rng, |ep| { + let disco1 = disco_shared.create_lying_discovery(ep.id()); + ConcurrentDiscovery::from_services(vec![Box::new(disco1)]) + }) + .await; // 10x faster test via a 3s idle timeout instead of the 30s default let mut config = TransportConfig::default(); @@ -935,16 +883,10 @@ mod tests { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); let disco_shared = TestDiscoveryShared::default(); - let (ep1, _guard1) = { - let secret = SecretKey::generate(&mut rng); - let disco = disco_shared.create_discovery(secret.public()); - new_endpoint(secret, disco).await - }; - let (ep2, _guard2) = { - let secret = SecretKey::generate(&mut rng); - let disco = disco_shared.create_discovery(secret.public()); - new_endpoint(secret, disco).await - }; + let (ep1, _guard1) = + new_endpoint(&mut rng, |ep| disco_shared.create_discovery(ep.id())).await; + let (ep2, _guard2) = + new_endpoint(&mut rng, |ep| disco_shared.create_discovery(ep.id())).await; let ep1_wrong_addr = EndpointAddr { endpoint_id: ep1.id(), @@ -955,18 +897,30 @@ mod tests { Ok(()) } - async fn new_endpoint( - secret: SecretKey, - disco: impl IntoDiscovery + 'static, + async fn new_endpoint D>( + rng: &mut R, + create_disco: F, ) -> (Endpoint, AbortOnDropHandle>) { - let ep = Endpoint::builder() + new_endpoint_add(rng, |ep| { + let disco = create_disco(ep); + ep.discovery().add(disco); + }) + .await + } + + async fn new_endpoint_add( + rng: &mut R, + add_disco: F, + ) -> (Endpoint, AbortOnDropHandle>) { + let secret = SecretKey::generate(rng); + + let ep = Endpoint::empty_builder(RelayMode::Disabled) .secret_key(secret) - .add_discovery(disco) - .relay_mode(RelayMode::Disabled) .alpns(vec![TEST_ALPN.to_vec()]) .bind() .await .unwrap(); + add_disco(&ep); let handle = tokio::spawn({ let ep = ep.clone(); @@ -1123,8 +1077,7 @@ mod test_dns_pkarr { dns_pkarr_server: &DnsPkarrServer, ) -> Result<(Endpoint, AbortOnDropHandle>)> { let secret_key = SecretKey::generate(rng); - let ep = Endpoint::builder() - .relay_mode(RelayMode::Custom(relay_map.clone())) + let ep = Endpoint::empty_builder(RelayMode::Custom(relay_map.clone())) .insecure_skip_relay_cert_verify(true) .secret_key(secret_key.clone()) .alpns(vec![TEST_ALPN.to_vec()]) diff --git a/iroh/src/discovery/dns.rs b/iroh/src/discovery/dns.rs index 3f661cc5dbc..bcfe90cef74 100644 --- a/iroh/src/discovery/dns.rs +++ b/iroh/src/discovery/dns.rs @@ -5,8 +5,9 @@ use iroh_relay::dns::DnsResolver; pub use iroh_relay::dns::{N0_DNS_ENDPOINT_ORIGIN_PROD, N0_DNS_ENDPOINT_ORIGIN_STAGING}; use n0_future::boxed::BoxStream; -use super::{DiscoveryContext, DiscoveryError, IntoDiscovery, IntoDiscoveryError}; +use super::{DiscoveryError, IntoDiscovery, IntoDiscoveryError}; use crate::{ + Endpoint, discovery::{Discovery, DiscoveryItem}, endpoint::force_staging_infra, }; @@ -93,12 +94,9 @@ impl DnsDiscovery { } impl IntoDiscovery for DnsDiscoveryBuilder { - fn into_discovery( - mut self, - context: &DiscoveryContext, - ) -> Result { + fn into_discovery(mut self, endpoint: &Endpoint) -> Result { if self.dns_resolver.is_none() { - self.dns_resolver = Some(context.dns_resolver().clone()); + self.dns_resolver = Some(endpoint.dns_resolver().clone()); } Ok(self.build()) } diff --git a/iroh/src/discovery/mdns.rs b/iroh/src/discovery/mdns.rs index bae122f607b..f5a30fe0bbd 100644 --- a/iroh/src/discovery/mdns.rs +++ b/iroh/src/discovery/mdns.rs @@ -9,7 +9,7 @@ //! use std::time::Duration; //! //! use iroh::{ -//! SecretKey, +//! RelayMode, SecretKey, //! discovery::mdns::{DiscoveryEvent, MdnsDiscovery}, //! endpoint::{Endpoint, Source}, //! }; @@ -18,7 +18,10 @@ //! #[tokio::main] //! async fn main() { //! let recent = Duration::from_secs(600); // 10 minutes in seconds -//! let endpoint = Endpoint::builder().bind().await.unwrap(); +//! let endpoint = Endpoint::empty_builder(RelayMode::Disabled) +//! .bind() +//! .await +//! .unwrap(); //! //! // Register the discovery services with the endpoint //! let mdns = MdnsDiscovery::builder().build(endpoint.id()).unwrap(); @@ -57,8 +60,11 @@ use swarm_discovery::{Discoverer, DropGuard, IpClass, Peer}; use tokio::sync::mpsc::{self, error::TrySendError}; use tracing::{Instrument, debug, error, info_span, trace, warn}; -use super::{DiscoveryContext, DiscoveryError, IntoDiscovery, IntoDiscoveryError}; -use crate::discovery::{Discovery, DiscoveryItem, EndpointData, EndpointInfo}; +use super::{DiscoveryError, IntoDiscovery, IntoDiscoveryError}; +use crate::{ + Endpoint, + discovery::{Discovery, DiscoveryItem, EndpointData, EndpointInfo}, +}; /// The n0 local service name const N0_SERVICE_NAME: &str = "irohv1"; @@ -195,11 +201,8 @@ impl Default for MdnsDiscoveryBuilder { } impl IntoDiscovery for MdnsDiscoveryBuilder { - fn into_discovery( - self, - context: &DiscoveryContext, - ) -> Result { - self.build(context.endpoint_id()) + fn into_discovery(self, endpoint: &Endpoint) -> Result { + self.build(endpoint.id()) } } diff --git a/iroh/src/discovery/pkarr.rs b/iroh/src/discovery/pkarr.rs index ea7c6cbeff4..1e1f0e54055 100644 --- a/iroh/src/discovery/pkarr.rs +++ b/iroh/src/discovery/pkarr.rs @@ -62,10 +62,11 @@ use snafu::{ResultExt, Snafu}; use tracing::{Instrument, debug, error_span, warn}; use url::Url; -use super::{DiscoveryContext, DiscoveryError, IntoDiscovery, IntoDiscoveryError}; +use super::{DiscoveryError, IntoDiscovery, IntoDiscoveryError}; #[cfg(not(wasm_browser))] use crate::dns::DnsResolver; use crate::{ + Endpoint, discovery::{Discovery, DiscoveryItem, EndpointData}, endpoint::force_staging_infra, util::reqwest_client_builder, @@ -203,16 +204,13 @@ impl PkarrPublisherBuilder { } impl IntoDiscovery for PkarrPublisherBuilder { - fn into_discovery( - mut self, - context: &DiscoveryContext, - ) -> Result { + fn into_discovery(mut self, endpoint: &Endpoint) -> Result { #[cfg(not(wasm_browser))] if self.dns_resolver.is_none() { - self.dns_resolver = Some(context.dns_resolver().clone()); + self.dns_resolver = Some(endpoint.dns_resolver().clone()); } - Ok(self.build(context.secret_key().clone())) + Ok(self.build(endpoint.secret_key().clone())) } } @@ -245,10 +243,10 @@ impl PkarrPublisher { /// time-to-live value for the published packets, and it will republish discovery information /// every [`DEFAULT_REPUBLISH_INTERVAL`], even if the information is unchanged. /// - /// [`PkarrPublisherBuilder`] implements [`IntoDiscovery`], so it can be passed to [`add_discovery`]. + /// [`PkarrPublisherBuilder`] implements [`IntoDiscovery`], so it can be passed to [`discovery`]. /// It will then use the endpoint's secret key to sign published packets. /// - /// [`add_discovery`]: crate::endpoint::Builder::add_discovery + /// [`discovery`]: crate::endpoint::Builder::discovery /// [pkarr]: https://pkarr.org pub fn builder(pkarr_relay: Url) -> PkarrPublisherBuilder { PkarrPublisherBuilder::new(pkarr_relay) @@ -437,13 +435,10 @@ impl PkarrResolverBuilder { } impl IntoDiscovery for PkarrResolverBuilder { - fn into_discovery( - mut self, - context: &DiscoveryContext, - ) -> Result { + fn into_discovery(mut self, endpoint: &Endpoint) -> Result { #[cfg(not(wasm_browser))] if self.dns_resolver.is_none() { - self.dns_resolver = Some(context.dns_resolver().clone()); + self.dns_resolver = Some(endpoint.dns_resolver().clone()); } Ok(self.build()) diff --git a/iroh/src/discovery/pkarr/dht.rs b/iroh/src/discovery/pkarr/dht.rs index 1b74a21bb6a..c00b1172422 100644 --- a/iroh/src/discovery/pkarr/dht.rs +++ b/iroh/src/discovery/pkarr/dht.rs @@ -18,9 +18,9 @@ use pkarr::{Client as PkarrClient, SignedPacket}; use url::Url; use crate::{ + Endpoint, discovery::{ - Discovery, DiscoveryContext, DiscoveryError, DiscoveryItem, EndpointData, IntoDiscovery, - IntoDiscoveryError, + Discovery, DiscoveryError, DiscoveryItem, EndpointData, IntoDiscovery, IntoDiscoveryError, pkarr::{DEFAULT_PKARR_TTL, N0_DNS_PKARR_RELAY_PROD, N0_DNS_PKARR_RELAY_STAGING}, }, endpoint_info::EndpointInfo, @@ -241,11 +241,8 @@ impl Builder { } impl IntoDiscovery for Builder { - fn into_discovery( - self, - context: &DiscoveryContext, - ) -> Result { - self.secret_key(context.secret_key().clone()).build() + fn into_discovery(self, endpoint: &Endpoint) -> Result { + self.secret_key(endpoint.secret_key().clone()).build() } } @@ -340,14 +337,12 @@ mod tests { use tracing_test::traced_test; use super::*; - use crate::Endpoint; #[tokio::test] #[ignore = "flaky"] #[traced_test] async fn dht_discovery_smoke() -> Result { - let ep = Endpoint::builder().bind().await?; - let secret = ep.secret_key().clone(); + let secret = SecretKey::generate(&mut rand::rng()); let testnet = pkarr::mainline::Testnet::new_async(3).await.e()?; let client = pkarr::Client::builder() .dht(|builder| builder.bootstrap(&testnet.bootstrap)) diff --git a/iroh/src/discovery/static_provider.rs b/iroh/src/discovery/static_provider.rs index c25ea2b41c8..4fee934b4a3 100644 --- a/iroh/src/discovery/static_provider.rs +++ b/iroh/src/discovery/static_provider.rs @@ -46,7 +46,7 @@ use super::{Discovery, DiscoveryError, DiscoveryItem, EndpointData, EndpointInfo /// let discovery = StaticProvider::new(); /// /// let _ep = Endpoint::builder() -/// .add_discovery(discovery.clone()) +/// .discovery(discovery.clone()) /// .bind() /// .await?; /// @@ -133,7 +133,7 @@ impl StaticProvider { /// // create a StaticProvider from the list of addrs. /// let discovery = StaticProvider::from_endpoint_info(addrs); /// // create an endpoint with the discovery - /// let endpoint = Endpoint::builder().add_discovery(discovery).bind().await?; + /// let endpoint = Endpoint::builder().discovery(discovery).bind().await?; /// # Ok(()) /// # } /// ``` @@ -238,14 +238,14 @@ mod tests { use n0_snafu::{Result, ResultExt}; use super::*; - use crate::Endpoint; + use crate::{Endpoint, RelayMode}; #[tokio::test] async fn test_basic() -> Result { let discovery = StaticProvider::new(); - let _ep = Endpoint::builder() - .add_discovery(discovery.clone()) + let _ep = Endpoint::empty_builder(RelayMode::Disabled) + .discovery(discovery.clone()) .bind() .await?; diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index d3fcb142ab3..77cd506216c 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -34,18 +34,20 @@ use url::Url; #[cfg(wasm_browser)] use crate::discovery::pkarr::PkarrResolver; #[cfg(not(wasm_browser))] -use crate::{discovery::dns::DnsDiscovery, dns::DnsResolver}; +use crate::dns::DnsResolver; use crate::{ discovery::{ ConcurrentDiscovery, DiscoveryError, DiscoveryTask, DynIntoDiscovery, IntoDiscovery, - UserData, pkarr::PkarrPublisher, + UserData, }, + endpoint::presets::Preset, magicsock::{self, EndpointIdMappedAddr, Handle, OwnAddressSnafu}, metrics::EndpointMetrics, net_report::Report, tls::{self, DEFAULT_MAX_TLS_TICKETS}, }; +pub mod presets; mod rtt_actor; // Missing still: SendDatagram and ConnectionClose::frame_type's Type. @@ -117,13 +119,31 @@ pub struct Builder { max_tls_tickets: usize, } -impl Default for Builder { - fn default() -> Self { +impl Builder { + // The ordering of public methods is reflected directly in the documentation. This is + // roughly ordered by what is most commonly needed by users. + + /// Creates a new [`Builder`] using the given [`Preset`]. + /// + /// See [`presets`] for more. + pub fn new(preset: P) -> Self { + Self::empty(RelayMode::Disabled).preset(preset) + } + + /// Applies the given [`Preset`]. + pub fn preset(mut self, preset: P) -> Self { + self = preset.apply(self); + self + } + + /// Creates an empty builder, which means there are no relays + /// and no discovery services configured. + pub fn empty(relay_mode: RelayMode) -> Self { let mut transport_config = quinn::TransportConfig::default(); transport_config.keep_alive_interval(Some(Duration::from_secs(1))); Self { secret_key: Default::default(), - relay_mode: default_relay_mode(), + relay_mode, alpn_protocols: Default::default(), transport_config, keylog: Default::default(), @@ -141,11 +161,6 @@ impl Default for Builder { max_tls_tickets: DEFAULT_MAX_TLS_TICKETS, } } -} - -impl Builder { - // The ordering of public methods is reflected directly in the documentation. This is - // roughly ordered by what is most commonly needed by users. // # The final constructor that everyone needs. @@ -173,7 +188,6 @@ impl Builder { addr_v6: self.addr_v6, secret_key, relay_map, - discovery: self.discovery, discovery_user_data: self.discovery_user_data, proxy_url: self.proxy_url, #[cfg(not(wasm_browser))] @@ -186,7 +200,24 @@ impl Builder { metrics, }; - Endpoint::bind(static_config, msock_opts).await + let msock = magicsock::MagicSock::spawn(msock_opts).await?; + trace!("created magicsock"); + debug!(version = env!("CARGO_PKG_VERSION"), "iroh Endpoint created"); + + let metrics = msock.metrics.magicsock.clone(); + let ep = Endpoint { + msock, + rtt_actor: Arc::new(rtt_actor::RttHandle::new(metrics)), + static_config: Arc::new(static_config), + }; + + // Add discovery mechanisms + for create_service in self.discovery { + let service = create_service.into_discovery(&ep)?; + ep.discovery().add_boxed(service); + } + + Ok(ep) } // # The very common methods everyone basically needs. @@ -260,24 +291,13 @@ impl Builder { } /// Removes all discovery services from the builder. - pub fn clear_discovery(mut self) -> Self { - self.discovery.clear(); - self - } - - /// Optionally sets a discovery mechanism for this endpoint. - /// - /// If you want to combine multiple discovery services, you can use - /// [`Builder::add_discovery`] instead. This will internally create a - /// [`crate::discovery::ConcurrentDiscovery`]. /// /// If no discovery service is set, connecting to an endpoint without providing its /// direct addresses or relay URLs will fail. /// /// See the documentation of the [`crate::discovery::Discovery`] trait for details. - pub fn discovery(mut self, discovery: impl IntoDiscovery) -> Self { + pub fn clear_discovery(mut self) -> Self { self.discovery.clear(); - self.discovery.push(Box::new(discovery)); self } @@ -297,66 +317,12 @@ impl Builder { /// To clear all discovery services, use [`Builder::clear_discovery`]. /// /// See the documentation of the [`crate::discovery::Discovery`] trait for details. - pub fn add_discovery(mut self, discovery: impl IntoDiscovery) -> Self { + pub fn discovery(mut self, discovery: impl IntoDiscovery) -> Self { self.discovery.push(Box::new(discovery)); self } - /// Configures the endpoint to use the default n0 DNS discovery service. - /// - /// The default discovery service publishes to and resolves from the - /// n0.computer dns server `iroh.link`. - /// - /// This is equivalent to adding both a [`crate::discovery::pkarr::PkarrPublisher`] - /// and a [`crate::discovery::dns::DnsDiscovery`], both configured to use the - /// n0.computer dns server. - /// - /// This will by default use [`N0_DNS_PKARR_RELAY_PROD`]. - /// When in tests, or when the `test-utils` feature is enabled, this will use the - /// [`N0_DNS_PKARR_RELAY_STAGING`]. - /// - /// [`N0_DNS_PKARR_RELAY_PROD`]: crate::discovery::pkarr::N0_DNS_PKARR_RELAY_PROD - /// [`N0_DNS_PKARR_RELAY_STAGING`]: crate::discovery::pkarr::N0_DNS_PKARR_RELAY_STAGING - pub fn discovery_n0(mut self) -> Self { - self = self.add_discovery(PkarrPublisher::n0_dns()); - // Resolve using HTTPS requests to our DNS server's /pkarr path in browsers - #[cfg(wasm_browser)] - { - self = self.add_discovery(PkarrResolver::n0_dns()); - } - // Resolve using DNS queries outside browsers. - #[cfg(not(wasm_browser))] - { - self = self.add_discovery(DnsDiscovery::n0_dns()); - } - self - } - - #[cfg(feature = "discovery-pkarr-dht")] - /// Configures the endpoint to also use the mainline DHT with default settings. - /// - /// This is equivalent to adding a [`crate::discovery::pkarr::dht::DhtDiscovery`] - /// with default settings. Note that DhtDiscovery has various more advanced - /// configuration options. If you need any of those, you should manually - /// create a DhtDiscovery and add it with [`Builder::add_discovery`]. - pub fn discovery_dht(mut self) -> Self { - self = self.add_discovery(crate::discovery::pkarr::dht::DhtDiscovery::builder()); - self - } - - #[cfg(feature = "discovery-local-network")] - /// Configures the endpoint to also use local network discovery. - /// - /// This is equivalent to adding a [`crate::discovery::mdns::MdnsDiscovery`] - /// with default settings. Note that MdnsDiscovery has various more advanced - /// configuration options. If you need any of those, you should manually - /// create a MdnsDiscovery and add it with [`Builder::add_discovery`]. - pub fn discovery_local_network(mut self) -> Self { - self = self.add_discovery(crate::discovery::mdns::MdnsDiscovery::builder()); - self - } - - /// Sets the initial user-defined data to be published in discovery services for this endpoint. + /// Sets the initial user-defined data to be published in discovery services for this node. /// /// When using discovery services, this string of [`UserData`] will be published together /// with the endpoint's addresses and relay URL. When other endpoints discover this endpoint, @@ -569,6 +535,10 @@ pub enum BindError { MagicSpawn { source: magicsock::CreateHandleError, }, + #[snafu(transparent)] + Discovery { + source: crate::discovery::IntoDiscoveryError, + }, } #[allow(missing_docs)] @@ -597,30 +567,24 @@ impl Endpoint { // # Methods relating to construction. /// Returns the builder for an [`Endpoint`], with a production configuration. + /// + /// This uses the [`presets::N0`] as the configuration. pub fn builder() -> Builder { - Builder::default() + Builder::new(presets::N0) } - /// Creates a quinn endpoint backed by a magicsock. + /// Returns the builder for an [`Endpoint`], with an empty configuration. /// - /// This is for internal use, the public interface is the [`Builder`] obtained from - /// [Self::builder]. See the methods on the builder for documentation of the parameters. - #[instrument("ep", skip_all, fields(me = %static_config.tls_config.secret_key.public().fmt_short()))] - async fn bind( - static_config: StaticConfig, - msock_opts: magicsock::Options, - ) -> Result { - let msock = magicsock::MagicSock::spawn(msock_opts).await?; - trace!("created magicsock"); - debug!(version = env!("CARGO_PKG_VERSION"), "iroh Endpoint created"); + /// See [`Builder::empty`] for details. + pub fn empty_builder(relay_mode: RelayMode) -> Builder { + Builder::empty(relay_mode) + } - let metrics = msock.metrics.magicsock.clone(); - let ep = Self { - msock, - rtt_actor: Arc::new(rtt_actor::RttHandle::new(metrics)), - static_config: Arc::new(static_config), - }; - Ok(ep) + /// Constructs a default [`Endpoint`] and binds it immediately. + /// + /// Uses the [`presets::N0`] as configuration. + pub async fn bind() -> Result { + Self::builder().bind().await } /// Sets the list of accepted ALPN protocols. @@ -987,7 +951,7 @@ impl Endpoint { /// /// # let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); /// # rt.block_on(async move { - /// let ep = Endpoint::builder().bind().await.unwrap(); + /// let ep = Endpoint::bind().await.unwrap(); /// let _report = ep.net_report().initialized().await; /// # }); /// ``` @@ -1071,7 +1035,7 @@ impl Endpoint { /// # use std::collections::BTreeMap; /// # use iroh::endpoint::Endpoint; /// # async fn wrapper() -> n0_snafu::Result { - /// let endpoint = Endpoint::builder().bind().await?; + /// let endpoint = Endpoint::bind().await?; /// assert_eq!(endpoint.metrics().magicsock.recv_datagrams.get(), 0); /// # Ok(()) /// # } @@ -1089,7 +1053,7 @@ impl Endpoint { /// # use iroh_metrics::{Metric, MetricsGroup, MetricValue, MetricsGroupSet}; /// # use iroh::endpoint::Endpoint; /// # async fn wrapper() -> n0_snafu::Result { - /// let endpoint = Endpoint::builder().bind().await?; + /// let endpoint = Endpoint::bind().await?; /// let metrics: BTreeMap = endpoint /// .metrics() /// .iter() @@ -1112,7 +1076,7 @@ impl Endpoint { /// # use iroh_metrics::{Registry, MetricsSource}; /// # use iroh::endpoint::Endpoint; /// # async fn wrapper() -> n0_snafu::Result { - /// let endpoint = Endpoint::builder().bind().await?; + /// let endpoint = Endpoint::bind().await?; /// let mut registry = Registry::default(); /// registry.register_all(endpoint.metrics()); /// let s = registry.encode_openmetrics_to_string()?; @@ -1149,7 +1113,7 @@ impl Endpoint { /// }); /// /// // Spawn an endpoint and add the metrics to the registry. - /// let endpoint = Endpoint::builder().bind().await?; + /// let endpoint = Endpoint::bind().await?; /// registry.write().unwrap().register_all(endpoint.metrics()); /// /// // Wait for the metrics server to bind, then fetch the metrics via HTTP. @@ -2190,7 +2154,7 @@ mod tests { #[tokio::test] #[traced_test] async fn test_connect_self() -> Result { - let ep = Endpoint::builder() + let ep = Endpoint::empty_builder(RelayMode::Disabled) .alpns(vec![TEST_ALPN.to_vec()]) .bind() .await @@ -2212,10 +2176,9 @@ mod tests { let server_peer_id = server_secret_key.public(); // Wait for the endpoint to be started to make sure it's up before clients try to connect - let ep = Endpoint::builder() + let ep = Endpoint::empty_builder(RelayMode::Custom(relay_map.clone())) .secret_key(server_secret_key) .alpns(vec![TEST_ALPN.to_vec()]) - .relay_mode(RelayMode::Custom(relay_map.clone())) .insecure_skip_relay_cert_verify(true) .bind() .await?; @@ -2252,9 +2215,8 @@ mod tests { let client = tokio::spawn( async move { - let ep = Endpoint::builder() + let ep = Endpoint::empty_builder(RelayMode::Custom(relay_map)) .alpns(vec![TEST_ALPN.to_vec()]) - .relay_mode(RelayMode::Custom(relay_map)) .insecure_skip_relay_cert_verify(true) .bind() .await?; @@ -2312,11 +2274,10 @@ mod tests { let server_endpoint_id = server_secret_key.public(); // Make sure the server is bound before having clients connect to it: - let ep = Endpoint::builder() + let ep = Endpoint::empty_builder(RelayMode::Custom(relay_map.clone())) .insecure_skip_relay_cert_verify(true) .secret_key(server_secret_key) .alpns(vec![TEST_ALPN.to_vec()]) - .relay_mode(RelayMode::Custom(relay_map.clone())) .bind() .await?; // Also make sure the server has a working relay connection @@ -2361,10 +2322,9 @@ mod tests { let client_secret_key = SecretKey::generate(&mut rng); async { info!("client binding"); - let ep = Endpoint::builder() + let ep = Endpoint::empty_builder(RelayMode::Custom(relay_map.clone())) .alpns(vec![TEST_ALPN.to_vec()]) .insecure_skip_relay_cert_verify(true) - .relay_mode(RelayMode::Custom(relay_map.clone())) .secret_key(client_secret_key) .bind() .await?; @@ -2412,14 +2372,12 @@ mod tests { #[traced_test] async fn endpoint_send_relay() -> Result { let (relay_map, _relay_url, _guard) = run_relay_server().await?; - let client = Endpoint::builder() + let client = Endpoint::empty_builder(RelayMode::Custom(relay_map.clone())) .insecure_skip_relay_cert_verify(true) - .relay_mode(RelayMode::Custom(relay_map.clone())) .bind() .await?; - let server = Endpoint::builder() + let server = Endpoint::empty_builder(RelayMode::Custom(relay_map)) .insecure_skip_relay_cert_verify(true) - .relay_mode(RelayMode::Custom(relay_map)) .alpns(vec![TEST_ALPN.to_vec()]) .bind() .await?; @@ -2463,14 +2421,12 @@ mod tests { #[traced_test] async fn endpoint_relay_map_change() -> Result { let (relay_map, relay_url, _guard1) = run_relay_server().await?; - let client = Endpoint::builder() + let client = Endpoint::empty_builder(RelayMode::Custom(relay_map.clone())) .insecure_skip_relay_cert_verify(true) - .relay_mode(RelayMode::Custom(relay_map.clone())) .bind() .await?; - let server = Endpoint::builder() + let server = Endpoint::empty_builder(RelayMode::Custom(relay_map)) .insecure_skip_relay_cert_verify(true) - .relay_mode(RelayMode::Custom(relay_map)) .alpns(vec![TEST_ALPN.to_vec()]) .bind() .await?; @@ -2572,17 +2528,15 @@ mod tests { #[traced_test] async fn endpoint_bidi_send_recv() -> Result { let disco = StaticProvider::new(); - let ep1 = Endpoint::builder() + let ep1 = Endpoint::empty_builder(RelayMode::Disabled) .discovery(disco.clone()) .alpns(vec![TEST_ALPN.to_vec()]) - .relay_mode(RelayMode::Disabled) .bind() .await?; - let ep2 = Endpoint::builder() + let ep2 = Endpoint::empty_builder(RelayMode::Disabled) .discovery(disco.clone()) .alpns(vec![TEST_ALPN.to_vec()]) - .relay_mode(RelayMode::Disabled) .bind() .await?; @@ -2676,18 +2630,16 @@ mod tests { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42); let ep1_secret_key = SecretKey::generate(&mut rng); let ep2_secret_key = SecretKey::generate(&mut rng); - let ep1 = Endpoint::builder() + let ep1 = Endpoint::empty_builder(RelayMode::Custom(relay_map.clone())) .secret_key(ep1_secret_key) .insecure_skip_relay_cert_verify(true) .alpns(vec![TEST_ALPN.to_vec()]) - .relay_mode(RelayMode::Custom(relay_map.clone())) .bind() .await?; - let ep2 = Endpoint::builder() + let ep2 = Endpoint::empty_builder(RelayMode::Custom(relay_map)) .secret_key(ep2_secret_key) .insecure_skip_relay_cert_verify(true) .alpns(vec![TEST_ALPN.to_vec()]) - .relay_mode(RelayMode::Custom(relay_map)) .bind() .await?; @@ -2761,9 +2713,8 @@ mod tests { async fn test_direct_addresses_no_qad_relay() -> Result { let (relay_map, _, _guard) = run_relay_server_with(false).await.unwrap(); - let ep = Endpoint::builder() + let ep = Endpoint::empty_builder(RelayMode::Custom(relay_map)) .alpns(vec![TEST_ALPN.to_vec()]) - .relay_mode(RelayMode::Custom(relay_map)) .insecure_skip_relay_cert_verify(true) .bind() .await?; @@ -2774,10 +2725,9 @@ mod tests { } async fn spawn_0rtt_server(secret_key: SecretKey, log_span: tracing::Span) -> Result { - let server = Endpoint::builder() + let server = Endpoint::empty_builder(RelayMode::Disabled) .secret_key(secret_key) .alpns(vec![TEST_ALPN.to_vec()]) - .relay_mode(RelayMode::Disabled) .bind() .await?; @@ -2918,10 +2868,7 @@ mod tests { #[traced_test] async fn test_0rtt() -> Result { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42); - let client = Endpoint::builder() - .relay_mode(RelayMode::Disabled) - .bind() - .await?; + let client = Endpoint::empty_builder(RelayMode::Disabled).bind().await?; let server = spawn_0rtt_server(SecretKey::generate(&mut rng), info_span!("server")).await?; connect_client_0rtt_expect_err(&client, server.addr()).await?; @@ -2941,10 +2888,7 @@ mod tests { #[traced_test] async fn test_0rtt_non_consecutive() -> Result { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42); - let client = Endpoint::builder() - .relay_mode(RelayMode::Disabled) - .bind() - .await?; + let client = Endpoint::empty_builder(RelayMode::Disabled).bind().await?; let server = spawn_0rtt_server(SecretKey::generate(&mut rng), info_span!("server")).await?; connect_client_0rtt_expect_err(&client, server.addr()).await?; @@ -2969,10 +2913,7 @@ mod tests { #[traced_test] async fn test_0rtt_after_server_restart() -> Result { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42); - let client = Endpoint::builder() - .relay_mode(RelayMode::Disabled) - .bind() - .await?; + let client = Endpoint::empty_builder(RelayMode::Disabled).bind().await?; let server_key = SecretKey::generate(&mut rng); let server = spawn_0rtt_server(server_key.clone(), info_span!("server-initial")).await?; @@ -2997,8 +2938,8 @@ mod tests { #[tokio::test] #[traced_test] async fn graceful_close() -> Result { - let client = Endpoint::builder().bind().await?; - let server = Endpoint::builder() + let client = Endpoint::empty_builder(RelayMode::Disabled).bind().await?; + let server = Endpoint::empty_builder(RelayMode::Disabled) .alpns(vec![TEST_ALPN.to_vec()]) .bind() .await?; @@ -3040,15 +2981,13 @@ mod tests { use iroh_metrics::{MetricsSource, Registry}; let secret_key = SecretKey::from_bytes(&[0u8; 32]); - let client = Endpoint::builder() + let client = Endpoint::empty_builder(RelayMode::Disabled) .secret_key(secret_key) - .relay_mode(RelayMode::Disabled) .bind() .await?; let secret_key = SecretKey::from_bytes(&[1u8; 32]); - let server = Endpoint::builder() + let server = Endpoint::empty_builder(RelayMode::Disabled) .secret_key(secret_key) - .relay_mode(RelayMode::Disabled) .alpns(vec![TEST_ALPN.to_vec()]) .bind() .await?; @@ -3104,12 +3043,8 @@ mod tests { primary_connect_alpn: &[u8], secondary_connect_alpns: Vec>, ) -> Result>> { - let client = Endpoint::builder() - .relay_mode(RelayMode::Disabled) - .bind() - .await?; - let server = Endpoint::builder() - .relay_mode(RelayMode::Disabled) + let client = Endpoint::empty_builder(RelayMode::Disabled).bind().await?; + let server = Endpoint::empty_builder(RelayMode::Disabled) .alpns(accept_alpns) .bind() .await?; @@ -3198,10 +3133,7 @@ mod tests { #[tokio::test] #[traced_test] async fn watch_net_report() -> Result { - let endpoint = Endpoint::builder() - .relay_mode(RelayMode::Staging) - .bind() - .await?; + let endpoint = Endpoint::empty_builder(RelayMode::Staging).bind().await?; // can get a first report endpoint.net_report().updated().await?; @@ -3232,8 +3164,7 @@ mod tests { } async fn noop_server() -> Result<(Router, EndpointAddr)> { - let endpoint = Endpoint::builder() - .relay_mode(RelayMode::Disabled) + let endpoint = Endpoint::empty_builder(RelayMode::Disabled) .bind() .await .e()?; @@ -3260,8 +3191,7 @@ mod tests { .map(|addr| addr.endpoint_id) .collect::>(); let discovery = StaticProvider::from_endpoint_info(addrs); - let endpoint = Endpoint::builder() - .relay_mode(RelayMode::Disabled) + let endpoint = Endpoint::empty_builder(RelayMode::Disabled) .discovery(discovery) .bind() .await diff --git a/iroh/src/endpoint/presets.rs b/iroh/src/endpoint/presets.rs new file mode 100644 index 00000000000..412af114947 --- /dev/null +++ b/iroh/src/endpoint/presets.rs @@ -0,0 +1,73 @@ +//! Presets allow configuring an endpoint quickly with a chosen set of defaults. +//! +//! # Example +//! +//! ```no_run +//! # async fn wrapper() -> n0_snafu::Result { +//! use iroh::{Endpoint, RelayMode, Watcher, endpoint::presets}; +//! +//! let endpoint = Endpoint::empty_builder(RelayMode::Disabled) +//! .preset(presets::N0) +//! .bind() +//! .await?; +//! # let _ = endpoint; +//! # Ok(()) +//! # } +//! ``` + +use crate::{ + discovery::pkarr::PkarrPublisher, + endpoint::{Builder, default_relay_mode}, +}; + +/// Defines a preset +pub trait Preset { + /// Applies the configuration to the passed in [`Builder`]. + fn apply(self, builder: Builder) -> Builder; +} + +/// Configures the endpoint to use the n0 defaults +/// +/// Currently this consists of +/// - the DNS discovery service. +/// - the default relay servers provided by Number 0. +/// +/// The default discovery service publishes to and resolves from the +/// n0.computer dns server `iroh.link`. +/// +/// This is equivalent to adding both a [`crate::discovery::pkarr::PkarrPublisher`] +/// and a [`crate::discovery::dns::DnsDiscovery`], both configured to use the +/// n0.computer dns server. +/// +/// This will by default use [`N0_DNS_PKARR_RELAY_PROD`]. +/// When in tests, or when the `test-utils` feature is enabled, this will use the +/// [`N0_DNS_PKARR_RELAY_STAGING`]. +/// +/// [`N0_DNS_PKARR_RELAY_PROD`]: crate::discovery::pkarr::N0_DNS_PKARR_RELAY_PROD +/// [`N0_DNS_PKARR_RELAY_STAGING`]: crate::discovery::pkarr::N0_DNS_PKARR_RELAY_STAGING +#[derive(Debug, Copy, Clone, Default)] +pub struct N0; + +impl Preset for N0 { + fn apply(self, mut builder: Builder) -> Builder { + builder = builder.discovery(PkarrPublisher::n0_dns()); + // Resolve using HTTPS requests to our DNS server's /pkarr path in browsers + #[cfg(wasm_browser)] + { + use crate::discovery::pkarr::PkarrResolver; + + builder = builder.discovery(PkarrResolver::n0_dns()); + } + // Resolve using DNS queries outside browsers. + #[cfg(not(wasm_browser))] + { + use crate::discovery::dns::DnsDiscovery; + + builder = builder.discovery(DnsDiscovery::n0_dns()); + } + + builder = builder.relay_mode(default_relay_mode()); + + builder + } +} diff --git a/iroh/src/lib.rs b/iroh/src/lib.rs index 5c94e419aa5..e171e695bd9 100644 --- a/iroh/src/lib.rs +++ b/iroh/src/lib.rs @@ -12,7 +12,7 @@ //! # use n0_snafu::ResultExt; //! # async fn wrapper() -> n0_snafu::Result { //! let addr: EndpointAddr = todo!(); -//! let ep = Endpoint::builder().bind().await?; +//! let ep = Endpoint::bind().await?; //! let conn = ep.connect(addr, b"my-alpn").await?; //! let mut send_stream = conn.open_uni().await.context("unable to open uni")?; //! send_stream @@ -174,8 +174,8 @@ //! use n0_snafu::{Result, ResultExt}; //! //! async fn connect(addr: EndpointAddr) -> Result<()> { -//! // The Endpoint is the central object that manages an iroh endpoint. -//! let ep = Endpoint::builder().bind().await?; +//! // The Endpoint is the central object that manages an iroh node. +//! let ep = Endpoint::bind().await?; //! //! // Establish a QUIC connection, open a bi-directional stream, exchange messages. //! let conn = ep.connect(addr, b"hello-world").await?; diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 57dec999c26..07a8618478b 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -69,10 +69,7 @@ use crate::net_report::{IpMappedAddr, QuicConfig}; use crate::{ defaults::timeouts::NET_REPORT_TIMEOUT, disco::{self, SendAddr, TransactionId}, - discovery::{ - ConcurrentDiscovery, Discovery, DiscoveryContext, DynIntoDiscovery, EndpointData, - IntoDiscoveryError, UserData, - }, + discovery::{ConcurrentDiscovery, Discovery, EndpointData, UserData}, key::{DecryptionError, SharedSecret, public_ed_box, secret_ed_box}, magicsock::endpoint_map::RemoteInfo, metrics::EndpointMetrics, @@ -115,9 +112,6 @@ pub(crate) struct Options { /// The [`RelayMap`] to use, leave empty to not use a relay server. pub(crate) relay_map: RelayMap, - /// Optional endpoint discovery mechanisms. - pub(crate) discovery: Vec>, - /// Optional user-defined discovery data. pub(crate) discovery_user_data: Option, @@ -1359,10 +1353,6 @@ pub enum CreateHandleError { CreateNetmonMonitor { source: netmon::Error }, #[snafu(display("Failed to subscribe netmon monitor"))] SubscribeNetmonMonitor { source: netmon::Error }, - #[snafu(transparent)] - Discovery { - source: crate::discovery::IntoDiscoveryError, - }, } impl Handle { @@ -1373,7 +1363,6 @@ impl Handle { addr_v6, secret_key, relay_map, - discovery, discovery_user_data, #[cfg(not(wasm_browser))] dns_resolver, @@ -1386,21 +1375,7 @@ impl Handle { metrics, } = opts; - let discovery = { - let context = DiscoveryContext { - secret_key: &secret_key, - #[cfg(not(wasm_browser))] - dns_resolver: &dns_resolver, - }; - let discovery = discovery - .into_iter() - .map(|builder| builder.into_discovery(&context)) - .collect::, IntoDiscoveryError>>()?; - match discovery.len() { - 0 => ConcurrentDiscovery::default(), - _ => ConcurrentDiscovery::from_services(discovery), - } - }; + let discovery = ConcurrentDiscovery::default(); let addr_v4 = addr_v4.unwrap_or_else(|| SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)); @@ -2594,7 +2569,6 @@ mod tests { addr_v6: None, secret_key, relay_map: RelayMap::empty(), - discovery: Default::default(), proxy_url: None, dns_resolver: DnsResolver::new(), server_config, @@ -2644,10 +2618,9 @@ mod tests { let mut transport_config = quinn::TransportConfig::default(); transport_config.max_idle_timeout(Some(Duration::from_secs(10).try_into().unwrap())); - let endpoint = Endpoint::builder() + let endpoint = Endpoint::empty_builder(relay_mode) .secret_key(secret_key.clone()) .transport_config(transport_config) - .relay_mode(relay_mode) .alpns(vec![ALPN.to_vec()]) .bind() .await @@ -3130,7 +3103,6 @@ mod tests { addr_v6: None, secret_key: secret_key.clone(), relay_map: RelayMap::empty(), - discovery: Default::default(), discovery_user_data: None, dns_resolver, proxy_url: None, diff --git a/iroh/src/protocol.rs b/iroh/src/protocol.rs index 0d8dda79b46..c50fbe98fe4 100644 --- a/iroh/src/protocol.rs +++ b/iroh/src/protocol.rs @@ -3,14 +3,17 @@ //! ## Example //! //! ```no_run -//! # use iroh::{endpoint::{Connection, BindError}, protocol::{AcceptError, ProtocolHandler, Router}, Endpoint, EndpointAddr}; +//! # use iroh::{ +//! # endpoint::{Connection, BindError}, +//! # protocol::{AcceptError, ProtocolHandler, Router}, +//! # Endpoint, +//! # EndpointAddr +//! # }; //! # //! # async fn test_compile() -> Result<(), BindError> { -//! let endpoint = Endpoint::builder().discovery_n0().bind().await?; +//! let endpoint = Endpoint::bind().await?; //! -//! let router = Router::builder(endpoint) -//! .accept(b"/my/alpn", Echo) -//! .spawn(); +//! let router = Router::builder(endpoint).accept(b"/my/alpn", Echo).spawn(); //! # Ok(()) //! # } //! @@ -71,7 +74,7 @@ use crate::{ /// # use iroh::{endpoint::Connecting, protocol::{ProtocolHandler, Router}, Endpoint, EndpointAddr}; /// # /// # async fn test_compile() -> n0_snafu::Result<()> { -/// let endpoint = Endpoint::builder().discovery_n0().bind().await?; +/// let endpoint = Endpoint::bind().await?; /// /// let router = Router::builder(endpoint) /// // .accept(&ALPN, ) @@ -582,7 +585,7 @@ mod tests { #[tokio::test] async fn test_shutdown() -> Result { - let endpoint = Endpoint::builder().bind().await?; + let endpoint = Endpoint::empty_builder(RelayMode::Disabled).bind().await?; let router = Router::builder(endpoint.clone()).spawn(); assert!(!router.is_shutdown()); @@ -620,20 +623,14 @@ mod tests { #[tokio::test] async fn test_limiter() -> Result { // tracing_subscriber::fmt::try_init().ok(); - let e1 = Endpoint::builder() - .relay_mode(RelayMode::Disabled) - .bind() - .await?; + let e1 = Endpoint::empty_builder(RelayMode::Disabled).bind().await?; // deny all access let proto = AccessLimit::new(Echo, |_endpoint_id| false); let r1 = Router::builder(e1.clone()).accept(ECHO_ALPN, proto).spawn(); let addr1 = r1.endpoint().addr(); dbg!(&addr1); - let e2 = Endpoint::builder() - .relay_mode(RelayMode::Disabled) - .bind() - .await?; + let e2 = Endpoint::empty_builder(RelayMode::Disabled).bind().await?; println!("connecting"); let conn = e2.connect(addr1, ECHO_ALPN).await?; @@ -673,10 +670,7 @@ mod tests { } eprintln!("creating ep1"); - let endpoint = Endpoint::builder() - .relay_mode(RelayMode::Disabled) - .bind() - .await?; + let endpoint = Endpoint::empty_builder(RelayMode::Disabled).bind().await?; let router = Router::builder(endpoint) .accept(TEST_ALPN, TestProtocol::default()) .spawn(); @@ -684,10 +678,7 @@ mod tests { let addr = router.endpoint().addr(); eprintln!("creating ep2"); - let endpoint2 = Endpoint::builder() - .relay_mode(RelayMode::Disabled) - .bind() - .await?; + let endpoint2 = Endpoint::empty_builder(RelayMode::Disabled).bind().await?; eprintln!("connecting to {addr:?}"); let conn = endpoint2.connect(addr, TEST_ALPN).await?; diff --git a/iroh/tests/integration.rs b/iroh/tests/integration.rs index d3f435e4feb..3d588296e2a 100644 --- a/iroh/tests/integration.rs +++ b/iroh/tests/integration.rs @@ -38,12 +38,10 @@ async fn simple_endpoint_id_based_connection_transfer() -> Result { let client = Endpoint::builder() .relay_mode(RelayMode::Staging) - .discovery_n0() .bind() .await?; let server = Endpoint::builder() .relay_mode(RelayMode::Staging) - .discovery_n0() .alpns(vec![ECHO_ALPN.to_vec()]) .bind() .await?;