n0-computer/iroh · error · Error
NoObservedAddr
Error message
NoObservedAddr
What it means
Error::NoObservedAddr from iroh-relay's QUIC address discovery (QAD). get_addr_and_latency awaits the relay/QUIC endpoint to report an externally observed address, and the stream ended without ever yielding one, so the bail fires. It means address discovery failed — the client could not learn its public address.
Solutions
- Retry address discovery against a responsive relay/QAD server.
- Check that UDP/QUIC traffic to the relay is not blocked by firewall or NAT.
- Fall back to other discovery mechanisms (e.g. relay-based observed address, STUN-like discovery) or configured external addresses.
- Update iroh versions on both ends to ensure compatible QUIC address-discovery behavior.
Example fix
match endpoint.discover_observed_addr().await {
Ok((addr, latency)) => /* use addr */,
Err(e) if e.to_string().contains("NoObservedAddr") => {
// fall back to relay-reported address or retry another relay
}
} Defensive patterns
Strategy: fallback
Validate before calling
// Pre-check relay reachability before QAD
if tokio::time::timeout(DISCOVERY_TIMEOUT, conn probe).await.is_err() {
// skip this relay, try another
} Try / catch
match get_addr_and_latency(...).await {
Err(e) if e.to_string().contains("NoObservedAddr") => use_fallback_addr_discovery(),
other => other,
} Prevention
- Ensure UDP/QUIC to the relay is not firewalled
- Configure multiple relays so discovery can fail over
- Set a timeout around discovery so unresponsive servers are dropped quickly
- Keep client and relay iroh versions compatible
When it happens
Trigger: Calling the QUIC address-discovery flow (get_addr_and_latency) when the server never sends an observed address: e.g. an unresponsive or misbehaving QAD server, a delayed connect that never completes the address-disc session, or the connection closing before external_addresses yields.
Common situations: NAT/firewall setups that block QUIC address discovery; relay servers that accept the connection but never answer address discovery; tests like test_qad_client_closes_unresponsive_fast and test_qad_connect_delayed simulate exactly these server misbehaviors.
Understand the failure class
Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.
Related errors
- NetworkDown
- The relay is rate-limiting this endpoint; outbound relay…
- Only a single default address can be set per IP family
- Invalid transport configuration
- UnexpectedUpgradeStatus
AI-assisted analysis of n0-computer/iroh@2b4de030ce (2026-09-08).
Data as JSON: /api/errors/7f03fef03118970c.
Report an issue: GitHub.
Appendix: source
Thrown at iroh-relay/src/quic.rs:342
let conn = connecting?.await?;
let mut external_addresses = conn.observed_external_addr();
// TODO(ramfox): I'd like to be able to cancel this so we can close cleanly
// if there the task that runs this function gets aborted.
// tokio::select! {
// _ = cancel.cancelled() => {
// conn.close(QUIC_ADDR_DISC_CLOSE_CODE, QUIC_ADDR_DISC_CLOSE_REASON);
// bail_any!("QUIC address discovery canceled early");
// },
// res = external_addresses.wait_for(|addr| addr.is_some()) => {
// let addr = res?.expect("checked");
// let latency = conn.rtt() / 2;
// // gracefully close the connections
// conn.close(QUIC_ADDR_DISC_CLOSE_CODE, QUIC_ADDR_DISC_CLOSE_REASON);
// Ok((addr, latency))
// }
let Some(mut observed_addr) = external_addresses.next().await else {
n0_error::bail!(Error::NoObservedAddr);
};
// if we've sent to an ipv4 address, but received an observed address
// that is ivp6 then the address is an [IPv4-Mapped IPv6 Addresses](https://doc.rust-lang.org/beta/std/net/struct.Ipv6Addr.html#ipv4-mapped-ipv6-addresses)
observed_addr = SocketAddr::new(observed_addr.ip().to_canonical(), observed_addr.port());
let latency = conn.rtt(PathId::ZERO).unwrap_or_default();
// gracefully close the connections
conn.close(QUIC_ADDR_DISC_CLOSE_CODE, QUIC_ADDR_DISC_CLOSE_REASON);
Ok((observed_addr, latency))
}
/// Create a connection usable for qad
pub async fn create_conn(
&self,
server_addr: SocketAddr,
host: &str,
) -> Result<noq::Connection, Error> {
let config = self.client_config.clone();
let connecting = self.ep.connect_with(config, server_addr, host);View on GitHub (pinned to 2b4de030ce)