From e61397ca8572c1bb58d1957fc4bd92af2eebbceb Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Sun, 12 Apr 2026 10:23:36 +0400 Subject: [PATCH] fix(connect): remove pre-Phase-6 same-IP heuristic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit de007ec added a heuristic that forced relay-only when peers had different public IPs. That was a stopgap for the race condition where one side picked Direct and the other picked Relay. Phase 6 (f5542ef) solved this properly via MediaPathReport negotiation, but the heuristic wasn't cleaned up and was still running BEFORE the Phase 6 code — suppressing the race entirely for cross-network calls. Removed. Phase 6 negotiation now handles ALL cases: both sides race, exchange reports, and agree on the same path before committing media. Cross-network calls that can't go P2P will have both sides report direct_ok=false and agree on relay. Co-Authored-By: Claude Opus 4.6 (1M context) --- desktop/src-tauri/src/lib.rs | 50 +++--------------------------------- 1 file changed, 4 insertions(+), 46 deletions(-) diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 35cd8d1..1161e65 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -372,52 +372,10 @@ async fn connect( ); // Phase 5.6 safety heuristic: only attempt P2P direct when - // both peers are behind the SAME public IP (= same LAN / same - // NAT). When they have DIFFERENT public IPs they're on - // different networks, and the current dual-path race has a - // race condition: one side may pick Direct while the other - // picks Relay, sending media to different places (TX > 0, - // RX: 0 on both — the screenshots that triggered this fix). - // - // Same public IP means: - // - Same LAN behind the same router → LAN candidates work - // - Same WAN NAT → reflex addr from the relay is the same - // endpoint for both peers, direct dial should work - // - // Different public IPs means: - // - Cross-network (WiFi ↔ LTE, home ↔ office, etc.) - // - Direct P2P requires symmetric hole-punching which the - // current architecture can't guarantee both sides agree on - // - Relay-only is the safe choice until ICE negotiation is - // implemented (Phase 6) - // - // This heuristic preserves same-LAN P2P (proven working) and - // eliminates the broken cross-network case. The full fix is - // ICE-style path negotiation where both sides agree on the - // winner before committing media. - if let Some(ref r) = role { - let same_public_ip = match ( - own_reflex_addr.as_deref().and_then(|s| s.parse::().ok()), - peer_addr_parsed, - ) { - (Some(own), Some(peer)) => own.ip() == peer.ip(), - _ => false, - }; - if !same_public_ip { - tracing::info!( - ?r, - own = ?own_reflex_addr, - peer = ?peer_direct_addr, - "connect: different public IPs → skipping P2P direct (relay-only until ICE negotiation is implemented)" - ); - emit_call_debug(&app, "connect:cross_network_relay_only", serde_json::json!({ - "own_reflex": own_reflex_addr, - "peer_reflex": peer_direct_addr, - "reason": "different public IPs — both sides must use relay to avoid path mismatch", - })); - role = None; // forces relay-only path - } - } + // Phase 6 handles the path agreement through MediaPathReport + // exchange — the pre-Phase-6 heuristic that forced relay-only + // for different public IPs has been removed. Both sides now + // race freely and negotiate the result. // Phase 5.5: build the full peer candidate bundle (reflex + // LAN hosts). The dial_order helper will fan them out in