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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcJZZeJ3Nqb2MHxXuUadkb
This commit is contained in:
rustdesk
2026-09-15 14:47:33 +08:00
co-authored by Claude Fable 5.1
parent 29cf7cbe4d
commit daf84c2df3
+17
View File
@@ -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.