From daf84c2df33b3d5861d9a36600773638559d072c Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 15 Sep 2026 14:47:33 +0800 Subject: [PATCH] webrtc: cap the answerer's ICE binding requests at 74 (~15s) The ICE crate marks a candidate pair failed after 7 unanswered binding requests (~1.4s at the 200ms check interval) and stops sending on it. Behind an address-restricted NAT those outgoing requests are also what keep this side's mapping open to the remote's checks, so a browser peer whose checks arrive late -- after the answerer has already given up -- never gets a response and the direct pair is lost while a relay carries the session. Set max_binding_requests to 74 (~14.8s, just inside libwebrtc's 15s CONNECTION_WRITE_TIMEOUT) on the answerer only (remote_endpoint set), so the controlled side keeps checking for about as long as a Chrome controller does. The desktop offerer keeps the crate default as an unchanged comparison point. Needs the SettingEngine knob added in rustdesk-org/webrtc @ 80d5a205 (rustdesk-patches-0.13). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01QcJZZeJ3Nqb2MHxXuUadkb --- src/webrtc.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/webrtc.rs b/src/webrtc.rs index 30701d956..5bdbdcae0 100644 --- a/src/webrtc.rs +++ b/src/webrtc.rs @@ -549,6 +549,17 @@ impl WebRTCStream { ice_servers } + /// How many binding requests the answerer's ICE agent sends on a pair without a response + /// before giving up on it: 74 at the 200ms check interval is ~14.8s, just inside the 15s + /// persistence libwebrtc gives a pair (`CONNECTION_WRITE_TIMEOUT`), so both ends of a + /// browser session keep checking for about the same time. The ICE crate's default of 7 + /// (~1.4s) is a server-side choice that assumes the other side keeps knocking; behind an + /// address-restricted NAT those requests are also what keep this side's mapping open to + /// the remote's checks, so stopping early closes the door on a pair that would have + /// connected once the path cleared, however long the remote keeps trying. Bounded by + /// `CONNECT_TIMEOUT` (18s). + const ICE_MAX_BINDING_REQUESTS: u16 = 74; + /// Built and driven on `WEBRTC_RT`: every socket and background task the pc creates must /// belong to a runtime that outlives the session (see `WEBRTC_RT`). A caller that abandons /// this future mid-await leaves the setup task to finish there; the abandoned result then @@ -617,6 +628,12 @@ impl WebRTCStream { let mut s = SettingEngine::default(); s.detach_data_channels(); s.set_ice_multicast_dns_mode(MulticastDnsMode::Disabled); + // Answerer only: `remote_endpoint` is empty when this side is the offerer. The + // desktop controller keeps the crate default so it stays an unchanged comparison + // point while the persistence experiment runs on the controlled side. + if !remote_endpoint.is_empty() { + s.set_ice_max_binding_requests(Some(Self::ICE_MAX_BINDING_REQUESTS)); + } // fe80::/10 can only be bound together with a scope id, which `IpAddr` cannot carry, so // gathering one never yields a candidate - only a failed bind and a warning per address. // Spelled out because `is_unicast_link_local` is not stable on our MSRV.