#4510·iroh

Support multiple simultaneous home relays on one Endpoint

Author: hangoxCreated Sep 7, 2026Updated Sep 7, 2026

Motivation

We run a fleet of build-runner machines, some of which sit on two isolated networks at once (e.g. a wired LAN and a separate WiFi VLAN with no route between them). We'd like a single iroh::Endpoint to register as reachable via a relay on each network simultaneously, so clients on either network can reach the runner without requiring a routed path between the two networks.

Today Endpoint::builder().relay_mode(...) only supports one active relay transport — configuring a RelayMap with multiple entries just gives the endpoint multiple candidate relays to pick from for a single home, not multiple simultaneous homes.

Our patch (verified working at runtime)

We prototyped this against iroh 1.0.3 (5 files, ~190 changed lines) and confirmed it works end-to-end, including auth-token routing across relays and graceful handling of a partially-unreachable relay set:

  • Endpoint::builder().add_relay_transport(relay_map) — an additive method (vs. relay_mode(), which replaces) that pushes another TransportConfig::Relay onto the builder.
  • socket.rs::bind() — removed a hardcoded limit of exactly one relay transport; each transport's RelayActorConfig now carries the full, shared relay_map (so auth-token lookups work for cross-relay routing) plus a forced_home: Option<RelayUrl> that's only set when there's more than one relay transport (is_multihome = relay_transport_configs.len() > 1, so single-relay behavior is unchanged when this feature isn't used).
  • RelayActorConfig/ActiveRelayActoreffective_preferred_relay() returns forced_home when set, falling back to the existing preferred-relay logic otherwise.
Key excerpts from the patch (the two hunks that matter most)
rust
// endpoint.rs — additive builder method, doesn't touch relay_mode()'s behavior
pub fn add_relay_transport(mut self, relay_map: RelayMap) -> Self {
    self.transports.push(TransportConfig::Relay {
        relay_map,
        is_user_defined: true,
    });
    self
}
rust
// socket.rs::bind() — the gate that keeps single-relay behavior unchanged
let is_multihome = relay_transport_configs.len() > 1;
// ... for each relay transport config:
let forced_home = if is_multihome {
    rm.urls::<Vec<_>>().into_iter().next()
} else {
    None // existing preferred-relay selection logic applies as before
};

Our fork lives in a private/vendored location today, so we don't have a public URL for the full diff — happy to paste the complete patch inline or open a draft PR if that's more useful for review.

Why the existing public API isn't enough

  • RelayMap with multiple entries is a candidate set for one home relay, selected via RTT, not "be reachable on all of them at once." RelayMode::Custom doesn't change this.
  • There's no builder method to add a second relay transport — relay_mode() is explicitly documented as replacing the existing one.
  • Even if there were, bind() currently hard-errors past one relay transport config (relay_transport_configs.len() > 1 bails).

A separate observation along the way (not asserting it's a bug)

While prototyping, we noticed that EndpointAddr::relay_urls() / home_relay_status() never removes a relay URL once it's been assigned, even after that relay becomes permanently unreachable or is explicitly killed. This might be intentional (e.g. "once configured, always reported"), so we're not asserting it's a defect — just flagging it in case it's useful context; happy to open it as its own issue if maintainers think it's worth tracking separately from the multi-home question.

What we're offering to do

Happy to open a PR with our patch adapted to whatever shape maintainers prefer (e.g. Vec<RelayMap> in the builder instead of a new method), and to discuss whether this fits iroh's design direction at all before investing in a polished PR.