From 50c4e435de8991e75777b7fed9dd5aface28285a Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Fri, 4 Sep 2026 17:12:24 +0800 Subject: [PATCH] connection: apply the non-video send timeout once the type is known (#16063) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * connection: apply the non-video send timeout once the type is known `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 * connection: keep the raw port-forward local write at 120 s `SEND_TIMEOUT_OTHER` also bounded `forward.send` in `try_port_forward_loop`, the write to the local target, whose own idle timeout is an hour. Lowering it to 30 s made a target that stops draining for half a minute drop the whole tunnel. That write gets its own constant at the value it always had. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab * connection: keep the non-video send timeout at its long-standing 120 s Lowering `SEND_TIMEOUT_OTHER` to 30 s was a policy change on top of the bug fix, argued from the 30 s read timeout, which measures something else and cannot even run while a send is blocked. The constant goes back to `SEND_TIMEOUT_VIDEO * 10`, where it has been since 2021, and the raw port-forward loop's local write shares it again. What remains is the fix alone: the type-specific timeout is chosen once the login request has said what the connection is. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab --------- Co-authored-by: Claude Fable 5.1 --- src/server/connection.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/server/connection.rs b/src/server/connection.rs index bcdae795a..4767e05fc 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -585,13 +585,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 +2762,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)