iroh 1.0.0-rc.0 - The first release candidate
by Friedel Ziegelmayer & Rüdiger KlaehnWelcome to iroh@1.0.0-rc.0, the first release candidate of iroh, a modular networking stack in Rust for building direct connections between devices.
After four years of work and more than 50 releases, we finally have our first release candidate for 1.0.
We want to take this moment to thank all the external contributors and folks who file issues and use iroh every day, helping us to make iroh better. And of course a big thank you to the whole n0 team for their hard work over the past years, to finally get us here!
The changes in this release were all about making sure our API surface is as refined as we can make it, so we can commit to it in good conscience. We eliminated almost all types from pre-1.0 crates in reexports and exported types, with only a very small number left.
As a large portion of iroh's public API depends on noq, our QUIC implementation, it too has gotten the 1.0 release candidate treatment, and will be stabilized alongside iroh.
But of course we also landed more bug fixes and improvements to NAT traversal, so this is the best version of iroh yet.
📦 Sometimes things have to move out
Several optional features have been moved out of the iroh crate and repository. This allows us to have a different versioning and release schedule for these components. It is also helpful to reduce the number of optional features in iroh.
address_lookup::DhtAddressLookuphas moved to theiroh-mainline-address-lookupcrate in theiroh-address-lookupsrepo.address_lookup::MdnsAddressLookuphas moved to theiroh-mdns-address-lookupcrate in theiroh-address-lookupsrepo.protocol::AccessLimithas moved to theiroh-utilcrate in theiroh-utilrepo.
✨ Making the API better
Path observation API redesign
Since we added multipath in 0.96, observing connection paths went through PathWatcher, a single primitive that tried to serve two very different consumers: code that wants "what are the paths right now?" and code that wants "tell me when paths change". The Watcher returned its state by value and retained stats for closed paths to keep them visible to late subscribers.
We've now split that into two primitives that each answer one of these questions clearly:
Connection::paths()returns aPathList<'_>, a borrowed snapshot of currently-open paths. Because it's lifetime-bound to theConnection,Path::stats()is infallible. noq is still alive, so the stats are too.Connection::path_events()returns aPathEventStreamofOpened/Selected/Closed/Laggedevents. The stream is'static, doesn't keep the connection alive, and registers its subscription immediately, so the standard "subscribe then snapshot" pattern is race-free.
Closed paths are no longer retained on the connection. Final per-path stats are delivered inline on PathEvent::Closed, and consumers accumulate them themselves. See the transfer.rs example for the pattern.
let mut events = conn.path_events();
while let Some(event) = events.next().await {
match event {
PathEvent::Opened { id, remote_addr } => println!("path {id:?} opened via {remote_addr:?}"),
PathEvent::Selected { id, remote_addr } => println!("now using path {id:?} via {remote_addr:?}"),
PathEvent::Closed { id, last_stats, .. } => println!("path {id:?} closed (rtt {:?})", last_stats.rtt),
_ => {}
}
}
Incoming address
We changed the local address for an incoming request from an IP address to a new IncomingLocalAddr that properly handles relay and custom connections.
let incoming = endpoint.accept().await.context("no incoming")?;
match incoming.local_addr() {
IncomingLocalAddr::Ip(ip) => println!("direct IP, local ip: {ip:?}"),
IncomingLocalAddr::Relay { url } => println!("via relay {url}"),
IncomingLocalAddr::Custom(addr) => println!("via custom transport: {addr:?}"),
_ => {}
}
Non-exhaustive structs and enums
In many cases we have marked public structs and enums with #[non_exhaustive] to allow for future extension without breaking the public API. This is the reason both examples above end with a wildcard arm (_ => {}): PathEvent and IncomingLocalAddr are both #[non_exhaustive], so the compiler requires you to handle the case of variants we may add later.
📣 Call to the community
We tried very hard to get a good holepunching rate and high performance. At this point we rely on feedback from the community. If you are a user of our stable release 0.35 and did not follow our fast-paced development since then, now is the time to try out the release candidate and let us know if there are any issues.
🎉 The last steps on the Road to 1.0
We are almost there, can you feel it? But of course a release candidate is neither the end nor the finish line. Our current plan is to ship at least one more release candidate in the next weeks, based on feedback and improvements we see necessary. After that we hope to cut 1.0 with any final changes we need to make. So stay tuned, and follow along.
⚠️ Breaking Changes
iroh- removed
Incoming::local_ip. UseIncoming::local_addrinstead (#4182)iroh::endpoint::PathWatcher,PathInfo,PathInfoList,PathInfoListIter(#4188)iroh::endpoint::ConnectionInfo, replaced byiroh::endpoint::WeakConnectionHandle(#4189)iroh::endpoint::Source, nowpub(crate)and no longer part of the public API (#4227)iroh::metrics::PortmapMetricsre-export (#4235)iroh::metrics::EndpointMetrics::portmapperfield (#4235)iroh::endpoint::Written(noq re-export) (#4236)iroh::endpoint::Builder::transport_biasandiroh::endpoint::transports::TransportBias(#4234)
- changed
Connection::pathsreturnsPathList<'_>instead ofPathWatcher.Path<'_>replacesPathInfo.Path::statsis now infallible (#4188)ConnectionInfo::pathsis removed in favour ofConnectionInfo::path_events.ConnectionInfo::selected_pathnow returnsOption<TransportAddr>instead ofOption<PathInfo>(#4188)Connection::to_info() -> ConnectionInfois nowConnection::weak_handle() -> WeakConnectionHandle.WeakConnectionHandleexposes far fewer methods thanConnectionInfodid. UseWeakConnectionHandle::upgradeand call the correspondingConnectionmethods (#4189)EndpointHooks::after_handshakeparameter changed from&WeakConnectionHandleto&Connection(#4189)- The
OnClosedfuture returned fromWeakConnectionHandle::closednow resolves toiroh::endpoint::Closed, a struct with public fields for the close reason, connection stats, and path stats (#4229) QuicTransportConfig::set_max_remote_nat_traversal_addressesrenamed toQuicTransportConfig::max_remote_nat_traversal_addresses(#4213)iroh::endpoint::transportsis now gated on theunstable-custom-transportsfeature (#4228)- The
metricsfeature no longer enablesportmapper/metrics(#4235) iroh::endpoint::BeforeConnectOutcomeandiroh::endpoint::AfterHandshakeOutcomeare now#[non_exhaustive](#4226)
- added
Incoming::local_addrreturningIncomingLocalAddr(#4182)iroh::endpoint::Path,Paths,PathEvent(withOpened,Closed,Selected,Laggedvariants),PathEventStream,Connection::path_events,ConnectionInfo::path_events(#4188)iroh::endpoint::WeakConnectionHandle(#4189)iroh::endpoint::Closed(#4229)iroh::metrics::NetReportMetrics::portmap_attemptsandNetReportMetrics::portmap_external_address_updatedcounters (#4235)
- removed
iroh-base- changed
iroh_base::endpoint_addr::CustomAddrParseErroris now#[non_exhaustive](#4226)
- changed
iroh-relay- removed
tokio_rustls_acme::AcmeStatefrom the public API.tokio_rustls_acmeis no longer referenced by any public type (#4166)iroh_relay::server::TlsConfig::quic_bind_addr. The value fromQuicConfig::bind_addris always used now (#4166)CertConfig::Reloadingvariant. UseCertConfig::Manualwith arustls::ServerConfigwhose cert resolver performs reloading (e.g. one built withiroh_relay::server::reloading_resolver) (#4166)iroh_relay::server::Server::certificates. Callers that need the certs should keep them alongside whatever they pass toCertConfig::Manual(#4166)iroh_relay::client::ClientBuilder::query_param(key, value), replaced byClientBuilder::auth_token. Arbitrary URL query parameters on the upgrade request are no longer settable on the client (#4205)
- changed
iroh_relay::server::ServerConfig<EC, EA>,RelayConfig<EC, EA>,TlsConfig<EC, EA>,CertConfig<EC, EA>are no longer generic.TlsConfig'sserver_config: rustls::ServerConfigfield is removed. Therustls::ServerConfignow lives inside theCertConfigvariants (Manual::server_config, or injected viaLetsEncrypt::server_config_builder) (#4166)CertConfig::LetsEncryptrestructured:{ state: AcmeState<EC, EA> }→{ acme_config: AcmeConfig, server_config_builder: rustls::ConfigBuilder<rustls::ServerConfig, rustls::server::WantsServerCert> }.CertConfig::Manualrestructured:{ certs: Vec<CertificateDer<'static>> }→{ server_config: rustls::ServerConfig }(#4166)QuicConfig::server_configtype changed fromrustls::ServerConfigtoOption<rustls::ServerConfig>. WhenNone, the QUIC server reusesRelayConfig::tls. If neither is set,Server::spawnfails withSpawnError::QuicSpawn { source: QuicSpawnError::TlsNotConfigured }(#4166)Server::spawnis no longer generic:pub async fn spawn(config: ServerConfig) -> Result<Self, SpawnError>(#4166)AccessConfig::Restrictedclosure now takes&ClientRequestinstead ofEndpointId. Existing closures need to readrequest.endpoint_id(). Boxed closure type exposed asiroh_relay::server::AccessCheck(#4207)AccessConfig::is_allowedsignature is nowis_allowed(&self, request: &ClientRequest) -> bool(#4207)AccessCheckfuture may now borrow from its&ClientRequestargument:for<'a> Fn(&'a ClientRequest) -> Pin<Box<dyn Future<Output = Access> + Send + 'a>>instead ofFn(&ClientRequest) -> n0_future::future::Boxed<Access>. Closures that build the future withasync move { ... }.boxed()keep compiling (#4205)ServerConfig,RelayConfig,AccessConfig,QuicConfig,TlsConfig,Limits,ClientRateLimit,CertConfig,client::Config<S>,http::ProtocolVersion,protos::handshake::Mechanism(cfgserver) are now#[non_exhaustive](#4190, #4226)
- added
iroh_relay::server::AcmeConfig, a builder forCertConfig::LetsEncrypt. Constructors:AcmeConfig::new(directory_url),AcmeConfig::letsencrypt(production). Builder methods:domains,contact,cache_path(#4166)iroh_relay::client::ClientBuilder::auth_token(#4205)- Constructors for non-exhaustive types:
RelayConfig::new,QuicConfig::new,TlsConfig::new,ClientRateLimit::new,client::Config::new(#4226)
- removed
iroh-dns-server- removed
- changed
iroh_dns_server::config::ZoneStoreOptionsreplaced byiroh_dns_server::config::StoreConfig, with the same fields. The privatesigned_packets::Optionstype is no longer exposed (#4160)Config,HttpConfig,HttpsConfig,MetricsConfig,MainlineConfig,StoreConfig,CertMode,RateLimitConfigare now#[non_exhaustive](#4160)DnsConfigis now#[non_exhaustive]. AddedDnsConfig::new(port: u16, default_soa: String, default_ttl: u32, origins: Vec<String>) -> Self, which leavesbind_addr = Noneand the apexrr_a,rr_aaaa,rr_nsrecords atNone. There is noDefaultimpl: SOA, origins, and port are deployment-specific and have no neutral default (#4226)
- added
iroh_dns_server::Metricsandiroh_dns_server::Serverre-exports at the crate root (#4160)
- Build / MSRV
- MSRV bumped to 1.91 (#4237)
- Restructuring (moved out of the iroh repo)
address_lookup::DhtAddressLookup→iroh-mainline-address-lookupaddress_lookup::MdnsAddressLookup→iroh-mdns-address-lookupprotocol::AccessLimit→iroh-util
But wait, there's more!
Many bugs were squashed, and smaller features were added. For all those details, check out the full changelog: https://github.com/n0-computer/iroh/releases/tag/v1.0.0-rc.0.
If you want to know what is coming up, check out the milestones, and if you have any wishes, let us know about the issues! If you need help using iroh or just want to chat, please join us on discord! And to keep up with all things iroh, check out our Twitter, Mastodon, and Bluesky.
To get started, take a look at our docs, dive directly into the code, or chat with us in our discord channel.