fix three ways ws + WebRTC could not work in practice

Review of #15684 and hbb_common#579. Each of these left the code reading
correct while the feature did not function.

- The RelayResponse race classified P2P with `result.2 == "IPv6"`, but
  that site's futures are only ever the relay ("Relay"/"WebSocket") and
  the WebRTC branch's own "WebRTC" — so the predicate was constantly
  false. When the relay landed first the result was still right (the
  webrtc arm's `others_fut.is_none()` fallback), but when WebRTC
  connected FIRST it was parked as if it were a relay and the relay was
  committed on arrival, discarding a live direct connection. That is the
  LAN case: the better the network, the worse the outcome. Classify by
  what the label means, via is_direct_transport, and test both orderings
  — only the relay-first one was covered.

- handle_peer_info wrote "force-always-relay=Y" into the peer's saved
  config whenever force_relay was set, which now includes the WebSocket
  transport. One ws session therefore turned the peer into a permanent
  relay-by-policy peer, and relay-by-policy means Relay-only ICE, so
  WebRTC could never go direct to it again — the flagship path worked
  exactly once. Persist policy_relay, which is the user's choice; the
  transport is a property of this client, not of the peer.

- The answerer gated on this machine's enable-webrtc option, but that is
  LocalConfig: the UI process writes it and never syncs it over IPC,
  while handle_punch_hole runs in the server process, which on Windows
  resolves LocalConfig under a different profile and reads the
  private-server default of "N". The gate refused to answer in exactly
  the self-hosted deployments the transport exists for. Drop it: the
  answerer follows the request, like the udp/ipv6 legs, and the option
  still gates the feature where it can — an offer only exists because
  some controller had it enabled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ExUfAkYbq8UC9pQCiLy8TQ
This commit is contained in:
rustdesk
2026-08-08 11:00:47 +08:00
parent 7682335b88
commit 8b65186287
3 changed files with 58 additions and 10 deletions

View File

@@ -854,13 +854,16 @@ impl RendezvousMediator {
// 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
&& !WebRTCStream::endpoint_declares_all_ice(&ph.webrtc_sdp_offer);
// Unlike the udp/ipv6 legs - which deliberately just follow the request - WebRTC is
// gated on this machine's own option too: answering builds a pc that gathers ICE
// from this host, so a machine with WebRTC off must not be pulled into it.
let webrtc_relay_only =
ph.force_relay && !WebRTCStream::endpoint_declares_all_ice(&ph.webrtc_sdp_offer);
// Like the udp/ipv6 legs, the answerer follows the request and does not consult this
// machine's own enable-webrtc option. That option is LocalConfig, which the UI process
// writes and never syncs over IPC — this code runs in the server process, which on
// Windows resolves LocalConfig under a different profile entirely and would read the
// private-server default of "N", silently refusing to answer in exactly the self-hosted
// deployments the transport is for. The option still gates the feature where it can:
// an offer only exists because a controller had it enabled.
let webrtc_viable = !ph.webrtc_sdp_offer.is_empty()
&& crate::get_webrtc_enabled()
&& !Config::is_proxy()
&& (!webrtc_relay_only || WebRTCStream::has_turn_server());
let webrtc_sdp_answer = if webrtc_viable {