From 0c9d8eb9f989f64dcc7d06a60f0ebce05c6496e0 Mon Sep 17 00:00:00 2001 From: Mollusk Date: Sat, 23 May 2026 16:17:38 -0400 Subject: [PATCH] host: emit relay-only ticket (drop direct IP candidates) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The host ticket embedded every direct IP candidate the endpoint discovered — on this machine that was 10 addrs, 7 of them useless Docker-bridge gateways (172.16.0.0/12) plus LAN/public v4/v6. That bloated the ticket to ~320 chars and leaked local network topology to whoever received it. Keep only the endpoint id + relay URL (~140 chars). The relay coordinates hole-punching to a direct path after connect, so peer reachability is unchanged; the direct addrs in the ticket only ever shaved a moment off the first connection attempt, and n0 DNS discovery already publishes the full addr keyed by id as a backstop. Await endpoint.online() (15s cap) before building the ticket so the relay URL is reliably populated; a relay outage degrades to a possibly-incomplete ticket rather than a hang. Experimental — isolated on feat/short-ticket pending an end-to-end cross-machine connect test before merging to main. Co-Authored-By: Claude Opus 4.7 --- src/host/mod.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/host/mod.rs b/src/host/mod.rs index 9820e6d..5139562 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -4,8 +4,8 @@ mod serve; mod wayland; use anyhow::{Result, bail}; -use iroh::Endpoint; use iroh::endpoint::{Connection, presets}; +use iroh::{Endpoint, EndpointAddr}; use iroh_tickets::endpoint::EndpointTicket; use std::time::Duration; use tokio::sync::{mpsc, oneshot}; @@ -53,8 +53,26 @@ pub async fn run(opts: HostOpts) -> Result<()> { .bind() .await?; + // Relay-only ticket: wait for the home relay to connect, then keep only + // the endpoint id + relay URL and drop the direct IP candidates. The relay + // coordinates hole-punching to a direct path right after connect, so this + // doesn't change whether peers can reach each other — it just keeps the + // ticket short (~140 vs ~320 chars) and stops it from leaking LAN / + // Docker-bridge addresses to whoever receives the ticket. Awaiting online() + // first guarantees the relay URL is actually present (addr() right after + // bind can return before the relay handshake completes); the 15s cap means + // a relay outage degrades to a possibly-incomplete ticket rather than a hang + // (n0 DNS discovery still resolves the id in that case). + if tokio::time::timeout(Duration::from_secs(15), endpoint.online()) + .await + .is_err() + { + tracing::warn!("home relay not connected within 15s; ticket may be incomplete"); + } let addr = endpoint.addr(); - let ticket = EndpointTicket::new(addr); + let relay_only = + EndpointAddr::new(addr.id).with_addrs(addr.addrs.iter().filter(|a| a.is_relay()).cloned()); + let ticket = EndpointTicket::new(relay_only); let clipboard_ok = opts.interactive && copy_to_clipboard(&ticket.to_string()); print_host_banner(&ticket, display, &opts, &resolution, clipboard_ok);