diff --git a/libs/hbb_common b/libs/hbb_common index 7c4456be9..a992c646b 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit 7c4456be9bdb5c53df01ebeac5a36d67a6392a38 +Subproject commit a992c646bf0e34245f80fb230e2b93b1cdd1da02 diff --git a/src/client.rs b/src/client.rs index 5f4c76719..983c8bece 100644 --- a/src/client.rs +++ b/src/client.rs @@ -499,7 +499,10 @@ impl Client { // When this request carries an offer, `_start_inner` keeps its rendezvous socket solely // for trickle signaling; a separate offer-less request owns any TCP punch attempt. let webrtc_offerer = if Self::should_create_webrtc_offerer(&interface) { - match WebRTCStream::new("", interface.is_force_relay(), CONNECT_TIMEOUT).await { + // ICE policy follows relay-by-POLICY, not force_relay: under WebSocket the + // latter is set for the classic paths, but ICE opens its own sockets and may + // still go direct - that is the only P2P path ws deployments have. + match WebRTCStream::new("", interface.is_policy_relay(), CONNECT_TIMEOUT).await { Ok(stream) => Some(stream), Err(err) => { log::warn!("webrtc offerer setup failed: {}", err); @@ -604,10 +607,13 @@ impl Client { /// ws deployments where classic punching is forced to relay. Independent of the udp-punch /// option too — the offer rides any request, and a server or peer without WebRTC support /// drops the field, so the race simply proceeds without it. - /// Under force_relay the pc uses Relay-only ICE, which gathers nothing and can never connect - /// unless a TURN server is configured, so skip building a guaranteed-dead pc + STUN/TURN - /// gathering + answerer signaling in that case too. - /// When force_relay *and* TURN are configured, WebRTC via TURN is a valid "relayed" path: + /// Under relay-by-POLICY (force-always-relay option or an explicit relay request) the pc + /// uses Relay-only ICE, which gathers nothing and can never connect unless a TURN server + /// is configured, so skip building a guaranteed-dead pc + STUN/TURN gathering + answerer + /// signaling in that case. WebSocket-forced relay is deliberately NOT policy: ws only + /// tunnels the signaling/relay legs, so the offer keeps full ICE and may land a direct + /// connection - the flagship path for ws deployments (`webrtc_all_ice` tells the peer). + /// When policy relay *and* TURN are configured, WebRTC via TURN is a valid "relayed" path: /// `connect` keeps a WebRTC win instead of replacing it with the RustDesk relay, and the /// RelayResponse path races it without a P2P preference delay. Any request carrying an offer /// keeps its rendezvous socket for trickle signaling and never reuses it for TCP punching; a @@ -616,7 +622,7 @@ impl Client { if Config::is_proxy() { return false; } - if interface.is_force_relay() && !WebRTCStream::has_turn_server() { + if interface.is_policy_relay() && !WebRTCStream::has_turn_server() { return false; } true @@ -883,6 +889,9 @@ impl Client { socket_addr_v6: ipv6.1.unwrap_or_default(), switch_code, webrtc_sdp_offer: webrtc_sdp_offer.clone(), + // Full-ICE offer despite force_relay (ws transport): tells the controlled side + // its answer may gather every candidate type instead of requiring TURN. + webrtc_all_ice: !webrtc_sdp_offer.is_empty() && !interface.is_policy_relay(), ..Default::default() }); let webrtc_session_key = webrtc_offerer @@ -1064,10 +1073,12 @@ impl Client { Ok((Stream::WebRTC(raced), None, "WebRTC")) } .boxed(); - if interface.is_force_relay() { + if interface.is_policy_relay() { // Relay-only WebRTC can use only TURN, so it has no P2P advantage // over the RustDesk relay. Take the first successful relay instead // of delaying an already-ready result for the preference window. + // Policy, not force_relay: under ws the offer is full ICE and a + // direct path is exactly what the preference window exists for. connect_futures.push(webrtc_fut); select_ok(connect_futures).await.map(|r| r.0) } else { @@ -1415,9 +1426,10 @@ impl Client { // disarmed only at the successful return when WebRTC is the kept transport. let mut direct = !conn.is_err(); - // A WebRTC win under force_relay only happens when the pc was built with Relay-only ICE - // (TURN configured), which already honors the relay requirement — keep it instead of - // replacing it with the RustDesk relay. + // Keep a WebRTC win instead of replacing it with the RustDesk relay: under relay-by- + // policy the pc was built with Relay-only ICE (TURN configured), which already honors + // the relay requirement, and under ws-forced relay a direct full-ICE connection is the + // preferred outcome, not a violation. if (interface.is_force_relay() && typ != "WebRTC") || conn.is_err() { if !relay_server.is_empty() { let switch_code = interface.get_switch_code(); @@ -2584,6 +2596,13 @@ pub struct LoginConfigHandler { // reconnect before the real reboot disconnect. restart_remote_device_at: Option, pub force_relay: bool, + // The policy component of force_relay: the user's relay choice (option or explicit + // request) plus proxy, WITHOUT the WebSocket-transport component. ws kills classic + // TCP/UDP punching (force_relay stays set for those paths) but says nothing about + // ICE, so WebRTC decisions - offer ICE policy, prefer-P2P racing - key off this + // instead: relay-by-policy must stay Relay-only ICE, relay-by-transport may go + // direct over full ICE. + pub policy_relay: bool, pub direct: Option, pub received: bool, switch_uuid: Option, @@ -2696,11 +2715,11 @@ impl LoginConfigHandler { self.session_id = sid; self.supported_encoding = Default::default(); self.clear_restarting_remote_device(); - self.force_relay = + self.policy_relay = config::option2bool("force-always-relay", &self.get_option("force-always-relay")) || force_relay - || use_ws() || Config::is_proxy(); + self.force_relay = self.policy_relay || use_ws(); if let Some((real_id, server, key)) = &self.other_server { let other_server_key = self.get_option("other-server-key"); if !other_server_key.is_empty() && key.is_empty() { @@ -4592,6 +4611,10 @@ pub trait Interface: Send + Clone + 'static + Sized { self.get_lch().read().unwrap().force_relay } + fn is_policy_relay(&self) -> bool { + self.get_lch().read().unwrap().policy_relay + } + fn get_switch_code(&self) -> String { match self.get_lch().read().unwrap().switch_uuid.clone() { Some(u) if !u.is_empty() => { diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index f5d51525b..3db47f7d3 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -689,13 +689,13 @@ impl RendezvousMediator { async fn spawn_webrtc_answerer( &self, ph: &PunchHole, - force_relay: bool, + relay_only_ice: bool, server: ServerPtr, peer_addr: SocketAddr, meta: ConnectionMeta, ) -> ResultType { let mut stream = - WebRTCStream::new(&ph.webrtc_sdp_offer, force_relay, CONNECT_TIMEOUT).await?; + WebRTCStream::new(&ph.webrtc_sdp_offer, relay_only_ice, CONNECT_TIMEOUT).await?; let answer = match stream.get_local_endpoint_trickle().await { Ok(answer) => answer, Err(e) => { @@ -849,14 +849,18 @@ impl RendezvousMediator { // and STUN bypass the proxy and leak the real IP. WebSocket mode does NOT disable it — // ws only tunnels the signaling/relay legs to the server, classic punching stays forced // to relay (`relay` above), and the answer rides the RelayResponse, leaving ICE as the - // only P2P path there. force_relay is different: relay-only ICE is viable with TURN. + // only P2P path there. force_relay depends on why it was set: with webrtc_all_ice the + // controller's relay is transport-forced (ws) and its offer carries every candidate + // type, so answer with full ICE and let a direct pair form; without it the offer is + // Relay-only ICE by policy, viable (and answerable) only through TURN. + let webrtc_relay_only = ph.force_relay && !ph.webrtc_all_ice; let webrtc_viable = !ph.webrtc_sdp_offer.is_empty() && !Config::is_proxy() - && (!ph.force_relay || WebRTCStream::has_turn_server()); + && (!webrtc_relay_only || WebRTCStream::has_turn_server()); let webrtc_sdp_answer = if webrtc_viable { self.spawn_webrtc_answerer( &ph, - ph.force_relay, + webrtc_relay_only, server.clone(), peer_addr, meta.clone(), diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 03b59a497..f7ca0e083 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -1294,7 +1294,11 @@ impl Session { // override only if true if true == force_relay { - self.lc.write().unwrap().force_relay = true; + let mut lc = self.lc.write().unwrap(); + lc.force_relay = true; + // An explicit retry-via-relay is user policy, not transport necessity: keep + // WebRTC on Relay-only ICE for this round like any force-always-relay session. + lc.policy_relay = true; } self.lc.write().unwrap().peer_info = None; self.reconnect_count.fetch_add(1, Ordering::SeqCst);