From 070085a0015e004dfc0bcfcec9dc6532559f7043 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 9 Sep 2026 08:03:22 +0800 Subject: [PATCH] fix: deliver the displacement to an authorized port forward An authorized port forward leaves the main select loop as soon as its tunnel is up (`port_forward_socket.is_some()` right after the logon response) and spends the rest of its life in `try_port_forward_loop`, which only received `rx_from_cm`. So the `Displaced` its replacement sent over `tx_from_authed` was queued and never read, and the stale tunnel kept running - while the test below asserts a port forward displaces its own kind. Registration happens inside the logon response, before that break, so the connection is in `AUTHED_CONNS` and does get selected; only the delivery was missing. Every other kind stays in the main loop and was unaffected, the mux port forward included: the break tests `port_forward_socket`, which a mux tunnel does not set. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns --- src/server/connection.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/server/connection.rs b/src/server/connection.rs index 5a09c627d..81401d72b 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1109,7 +1109,10 @@ impl Connection { if conn.authorized { password::update_temporary_password(); } - if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await { + if let Err(err) = conn + .try_port_forward_loop(&mut rx_from_cm, &mut rx_from_authed) + .await + { conn.on_close(&err.to_string(), false).await; raii::AuthedConnID::check_remove_session(conn.inner.id(), conn.session_key()); } @@ -1215,6 +1218,7 @@ impl Connection { async fn try_port_forward_loop( &mut self, rx_from_cm: &mut mpsc::UnboundedReceiver, + rx_from_authed: &mut mpsc::UnboundedReceiver, ) -> ResultType<()> { let mut last_recv_time = Instant::now(); if let Some(mut forward) = self.port_forward_socket.take() { @@ -1242,6 +1246,14 @@ impl Connection { _ => {} } } + // An authorized port forward leaves the main select loop the moment its + // tunnel is up, so this is where it spends the rest of its life and the only + // place a displacement can still reach it. + Some(data) = rx_from_authed.recv() => { + if let ipc::Data::Displaced = data { + bail!("displaced by a newer connection"); + } + } res = forward.next() => { if let Some(res) = res { last_recv_time = Instant::now();