mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-11 06:51:00 +03:00
ws: decouple ICE policy from force_relay — full-ICE WebRTC over WebSocket
WebSocket support folds into force_relay because a ws tunnel kills classic TCP/UDP punching — but that conflated transport necessity with relay policy, and the WebRTC decisions keyed off the merged flag: a ws client built no offerer at all without TURN, and only a Relay-only-ICE one with it. ws deployments could never reach a direct WebRTC connection, which is exactly the path they are supposed to live on. Split the flag. LoginConfigHandler now tracks policy_relay (the force-always-relay option, an explicit relay request — /r ids and retry-via-relay included — and proxy) separately; force_relay stays policy_relay || use_ws() and keeps governing the classic paths, so non-ws behavior is unchanged everywhere: - the offerer's existence and ICE policy follow policy_relay: under pure ws the offer gathers every candidate type and may go direct; under relay-by-policy it stays Relay-only ICE, TURN-gated, exactly as before; - the RelayResponse race applies the prefer-P2P window under ws (a direct ICE path is worth delaying an already-ready relay for) while policy relay keeps first-success semantics; - the request carries webrtc_all_ice (hbb_common 64b54ab) so the controlled side knows the offer is full-ICE: it answers with full ICE and no TURN requirement, while offers without the bit keep today's relay-only answer path on every version-skew combination. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ExUfAkYbq8UC9pQCiLy8TQ
This commit is contained in:
Submodule libs/hbb_common updated: 7c4456be9b...a992c646bf
@@ -499,7 +499,10 @@ impl Client {
|
|||||||
// When this request carries an offer, `_start_inner` keeps its rendezvous socket solely
|
// 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.
|
// for trickle signaling; a separate offer-less request owns any TCP punch attempt.
|
||||||
let webrtc_offerer = if Self::should_create_webrtc_offerer(&interface) {
|
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),
|
Ok(stream) => Some(stream),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("webrtc offerer setup failed: {}", 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
|
/// 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
|
/// 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.
|
/// 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
|
/// Under relay-by-POLICY (force-always-relay option or an explicit relay request) the pc
|
||||||
/// unless a TURN server is configured, so skip building a guaranteed-dead pc + STUN/TURN
|
/// uses Relay-only ICE, which gathers nothing and can never connect unless a TURN server
|
||||||
/// gathering + answerer signaling in that case too.
|
/// is configured, so skip building a guaranteed-dead pc + STUN/TURN gathering + answerer
|
||||||
/// When force_relay *and* TURN are configured, WebRTC via TURN is a valid "relayed" path:
|
/// 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
|
/// `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
|
/// 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
|
/// 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() {
|
if Config::is_proxy() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if interface.is_force_relay() && !WebRTCStream::has_turn_server() {
|
if interface.is_policy_relay() && !WebRTCStream::has_turn_server() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@@ -883,6 +889,9 @@ impl Client {
|
|||||||
socket_addr_v6: ipv6.1.unwrap_or_default(),
|
socket_addr_v6: ipv6.1.unwrap_or_default(),
|
||||||
switch_code,
|
switch_code,
|
||||||
webrtc_sdp_offer: webrtc_sdp_offer.clone(),
|
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()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let webrtc_session_key = webrtc_offerer
|
let webrtc_session_key = webrtc_offerer
|
||||||
@@ -1064,10 +1073,12 @@ impl Client {
|
|||||||
Ok((Stream::WebRTC(raced), None, "WebRTC"))
|
Ok((Stream::WebRTC(raced), None, "WebRTC"))
|
||||||
}
|
}
|
||||||
.boxed();
|
.boxed();
|
||||||
if interface.is_force_relay() {
|
if interface.is_policy_relay() {
|
||||||
// Relay-only WebRTC can use only TURN, so it has no P2P advantage
|
// Relay-only WebRTC can use only TURN, so it has no P2P advantage
|
||||||
// over the RustDesk relay. Take the first successful relay instead
|
// over the RustDesk relay. Take the first successful relay instead
|
||||||
// of delaying an already-ready result for the preference window.
|
// 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);
|
connect_futures.push(webrtc_fut);
|
||||||
select_ok(connect_futures).await.map(|r| r.0)
|
select_ok(connect_futures).await.map(|r| r.0)
|
||||||
} else {
|
} else {
|
||||||
@@ -1415,9 +1426,10 @@ impl Client {
|
|||||||
// disarmed only at the successful return when WebRTC is the kept transport.
|
// disarmed only at the successful return when WebRTC is the kept transport.
|
||||||
|
|
||||||
let mut direct = !conn.is_err();
|
let mut direct = !conn.is_err();
|
||||||
// A WebRTC win under force_relay only happens when the pc was built with Relay-only ICE
|
// Keep a WebRTC win instead of replacing it with the RustDesk relay: under relay-by-
|
||||||
// (TURN configured), which already honors the relay requirement — keep it instead of
|
// policy the pc was built with Relay-only ICE (TURN configured), which already honors
|
||||||
// replacing it with the RustDesk relay.
|
// 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 (interface.is_force_relay() && typ != "WebRTC") || conn.is_err() {
|
||||||
if !relay_server.is_empty() {
|
if !relay_server.is_empty() {
|
||||||
let switch_code = interface.get_switch_code();
|
let switch_code = interface.get_switch_code();
|
||||||
@@ -2584,6 +2596,13 @@ pub struct LoginConfigHandler {
|
|||||||
// reconnect before the real reboot disconnect.
|
// reconnect before the real reboot disconnect.
|
||||||
restart_remote_device_at: Option<Instant>,
|
restart_remote_device_at: Option<Instant>,
|
||||||
pub force_relay: bool,
|
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<bool>,
|
pub direct: Option<bool>,
|
||||||
pub received: bool,
|
pub received: bool,
|
||||||
switch_uuid: Option<String>,
|
switch_uuid: Option<String>,
|
||||||
@@ -2696,11 +2715,11 @@ impl LoginConfigHandler {
|
|||||||
self.session_id = sid;
|
self.session_id = sid;
|
||||||
self.supported_encoding = Default::default();
|
self.supported_encoding = Default::default();
|
||||||
self.clear_restarting_remote_device();
|
self.clear_restarting_remote_device();
|
||||||
self.force_relay =
|
self.policy_relay =
|
||||||
config::option2bool("force-always-relay", &self.get_option("force-always-relay"))
|
config::option2bool("force-always-relay", &self.get_option("force-always-relay"))
|
||||||
|| force_relay
|
|| force_relay
|
||||||
|| use_ws()
|
|
||||||
|| Config::is_proxy();
|
|| Config::is_proxy();
|
||||||
|
self.force_relay = self.policy_relay || use_ws();
|
||||||
if let Some((real_id, server, key)) = &self.other_server {
|
if let Some((real_id, server, key)) = &self.other_server {
|
||||||
let other_server_key = self.get_option("other-server-key");
|
let other_server_key = self.get_option("other-server-key");
|
||||||
if !other_server_key.is_empty() && key.is_empty() {
|
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
|
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 {
|
fn get_switch_code(&self) -> String {
|
||||||
match self.get_lch().read().unwrap().switch_uuid.clone() {
|
match self.get_lch().read().unwrap().switch_uuid.clone() {
|
||||||
Some(u) if !u.is_empty() => {
|
Some(u) if !u.is_empty() => {
|
||||||
|
|||||||
@@ -689,13 +689,13 @@ impl RendezvousMediator {
|
|||||||
async fn spawn_webrtc_answerer(
|
async fn spawn_webrtc_answerer(
|
||||||
&self,
|
&self,
|
||||||
ph: &PunchHole,
|
ph: &PunchHole,
|
||||||
force_relay: bool,
|
relay_only_ice: bool,
|
||||||
server: ServerPtr,
|
server: ServerPtr,
|
||||||
peer_addr: SocketAddr,
|
peer_addr: SocketAddr,
|
||||||
meta: ConnectionMeta,
|
meta: ConnectionMeta,
|
||||||
) -> ResultType<String> {
|
) -> ResultType<String> {
|
||||||
let mut stream =
|
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 {
|
let answer = match stream.get_local_endpoint_trickle().await {
|
||||||
Ok(answer) => answer,
|
Ok(answer) => answer,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -849,14 +849,18 @@ impl RendezvousMediator {
|
|||||||
// and STUN bypass the proxy and leak the real IP. WebSocket mode does NOT disable it —
|
// 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
|
// 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
|
// 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()
|
let webrtc_viable = !ph.webrtc_sdp_offer.is_empty()
|
||||||
&& !Config::is_proxy()
|
&& !Config::is_proxy()
|
||||||
&& (!ph.force_relay || WebRTCStream::has_turn_server());
|
&& (!webrtc_relay_only || WebRTCStream::has_turn_server());
|
||||||
let webrtc_sdp_answer = if webrtc_viable {
|
let webrtc_sdp_answer = if webrtc_viable {
|
||||||
self.spawn_webrtc_answerer(
|
self.spawn_webrtc_answerer(
|
||||||
&ph,
|
&ph,
|
||||||
ph.force_relay,
|
webrtc_relay_only,
|
||||||
server.clone(),
|
server.clone(),
|
||||||
peer_addr,
|
peer_addr,
|
||||||
meta.clone(),
|
meta.clone(),
|
||||||
|
|||||||
@@ -1294,7 +1294,11 @@ impl<T: InvokeUiSession> Session<T> {
|
|||||||
|
|
||||||
// override only if true
|
// override only if true
|
||||||
if true == force_relay {
|
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.lc.write().unwrap().peer_info = None;
|
||||||
self.reconnect_count.fetch_add(1, Ordering::SeqCst);
|
self.reconnect_count.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|||||||
Reference in New Issue
Block a user