From 0d8a52bc5a00894d820e57e88830a4f4c16e679d Mon Sep 17 00:00:00 2001 From: rustdesk Date: Fri, 4 Sep 2026 09:56:47 +0800 Subject: [PATCH] connection: apply the non-video send timeout once the type is known MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Connection::start` set the send timeout before the login request had arrived, when `file_transfer`, `port_forward_socket` and `terminal` were all still unset, so every connection got `SEND_TIMEOUT_VIDEO` (12 s) and the `SEND_TIMEOUT_OTHER` branch never ran. A file transfer, terminal or port forward whose peer stopped draining for 12 s — a Wi-Fi roam, a VPN reconnect — was dropped. The type-specific timeout is now set in `on_message` right after the login request's union has been matched; `start` keeps the video figure for the login phase. `SEND_TIMEOUT_OTHER` also drops from 120 s to 30 s, the same horizon as the 30 s read timeout: the timeout wraps a single `send`, so it only fires when the peer makes no progress at all for that long, and beyond 30 s the read check would declare the same peer dead anyway. The raw port-forward pipe's write to its local target shares the constant and moves with it. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab --- src/server/connection.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/server/connection.rs b/src/server/connection.rs index bcdae795a..4b1c976ca 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -391,7 +391,9 @@ const SEC30: Duration = Duration::from_secs(30); const H1: Duration = Duration::from_secs(3600); const MILLI1: Duration = Duration::from_millis(1); const SEND_TIMEOUT_VIDEO: u64 = 12_000; -const SEND_TIMEOUT_OTHER: u64 = SEND_TIMEOUT_VIDEO * 10; +// The same horizon as the 30 s read timeout: a peer that has not drained a +// single message for that long is the same dead peer that check would catch. +const SEND_TIMEOUT_OTHER: u64 = 30_000; const SESSION_TIMEOUT: Duration = Duration::from_secs(30); /// Whether the DRM backend can serve a Wayland login screen here. @@ -585,13 +587,9 @@ impl Connection { crate::rustdesk_interval(time::interval_at(Instant::now(), TEST_DELAY_TIMEOUT)); let mut last_recv_time = Instant::now(); - conn.stream.set_send_timeout( - if conn.file_transfer.is_some() || conn.port_forward_socket.is_some() || conn.terminal { - SEND_TIMEOUT_OTHER - } else { - SEND_TIMEOUT_VIDEO - }, - ); + // The connection type is not known until the login request arrives; + // `on_message` picks the type-specific timeout then. + conn.stream.set_send_timeout(SEND_TIMEOUT_VIDEO); #[cfg(not(any(target_os = "android", target_os = "ios")))] std::thread::spawn(move || Self::handle_input(_rx_input, tx_cloned)); @@ -2766,6 +2764,17 @@ impl Connection { } } + self.stream.set_send_timeout( + if self.file_transfer.is_some() + || self.terminal + || matches!(self.lr.union, Some(login_request::Union::PortForward(_))) + { + SEND_TIMEOUT_OTHER + } else { + SEND_TIMEOUT_VIDEO + }, + ); + if !crate::common::is_direct_ip_access(&lr.username) && lr.username != Config::get_id() { self.send_login_error(crate::client::LOGIN_MSG_OFFLINE)