{"record":{"id":"7f03fef03118970c","repo":"n0-computer/iroh","slug":"noobservedaddr","errorCode":null,"errorMessage":"NoObservedAddr","messagePattern":"NoObservedAddr","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"iroh-relay/src/quic.rs","lineNumber":342,"sourceCode":"        let conn = connecting?.await?;\n        let mut external_addresses = conn.observed_external_addr();\n        // TODO(ramfox): I'd like to be able to cancel this so we can close cleanly\n        // if there the task that runs this function gets aborted.\n        // tokio::select! {\n        //     _ = cancel.cancelled() => {\n        //         conn.close(QUIC_ADDR_DISC_CLOSE_CODE, QUIC_ADDR_DISC_CLOSE_REASON);\n        //         bail_any!(\"QUIC address discovery canceled early\");\n        //     },\n        //     res = external_addresses.wait_for(|addr| addr.is_some()) => {\n        //         let addr = res?.expect(\"checked\");\n        //         let latency = conn.rtt() / 2;\n        //         // gracefully close the connections\n        //         conn.close(QUIC_ADDR_DISC_CLOSE_CODE, QUIC_ADDR_DISC_CLOSE_REASON);\n        //         Ok((addr, latency))\n        //     }\n\n        let Some(mut observed_addr) = external_addresses.next().await else {\n            n0_error::bail!(Error::NoObservedAddr);\n        };\n        // if we've sent to an ipv4 address, but received an observed address\n        // 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)\n        observed_addr = SocketAddr::new(observed_addr.ip().to_canonical(), observed_addr.port());\n        let latency = conn.rtt(PathId::ZERO).unwrap_or_default();\n        // gracefully close the connections\n        conn.close(QUIC_ADDR_DISC_CLOSE_CODE, QUIC_ADDR_DISC_CLOSE_REASON);\n        Ok((observed_addr, latency))\n    }\n\n    /// Create a connection usable for qad\n    pub async fn create_conn(\n        &self,\n        server_addr: SocketAddr,\n        host: &str,\n    ) -> Result<noq::Connection, Error> {\n        let config = self.client_config.clone();\n        let connecting = self.ep.connect_with(config, server_addr, host);","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/n0-computer/iroh/blob/2b4de030ce5e0133f272871a76f0c685c63f552a/iroh-relay/src/quic.rs#L324-L360","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"match endpoint.discover_observed_addr().await {\n    Ok((addr, latency)) => /* use addr */,\n    Err(e) if e.to_string().contains(\"NoObservedAddr\") => {\n        // fall back to relay-reported address or retry another relay\n    }\n}","handlingStrategy":"fallback","validationCode":"// Pre-check relay reachability before QAD\nif tokio::time::timeout(DISCOVERY_TIMEOUT, conn probe).await.is_err() {\n    // skip this relay, try another\n}","typeGuard":null,"tryCatchPattern":"match get_addr_and_latency(...).await {\n    Err(e) if e.to_string().contains(\"NoObservedAddr\") => use_fallback_addr_discovery(),\n    other => other,\n}","preventionTips":["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"],"tags":["network","quic","address-discovery","nat"],"backgroundTag":"empty-result-set","analyzedSha":"2b4de030ce5e0133f272871a76f0c685c63f552a","analyzedAt":"2026-09-08T04:26:47.755Z","contentChangedAt":"2026-09-08T04:26:47.755Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}