From 9409912344bc5106c6a84e058fdc769aba657c9b Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 25 Jul 2025 13:22:52 +0800 Subject: [PATCH] update kcp-sys (#12419) 1. Update kcp-sys to send KCP in frames to avoid potential crashes. 2. Fix the issue when the controling side is closed, the kcp connection close is not immediately recognized by the controlled end. * Unless the controling side receives the close reason, force the sending of the close reason to the controlled end when using KCP, and delay for 30ms to ensure the message is sent successfully. * Move the CloseReason receiving forward, as this message needs to be received when unauthorized, especially for kcp. Signed-off-by: 21pages --- Cargo.lock | 3 ++- src/client/io_loop.rs | 30 ++++++++++++++++++++++++------ src/server/connection.rs | 19 ++++++++++--------- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2792b60d9..c00b0ade1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3651,7 +3651,7 @@ dependencies = [ [[package]] name = "kcp-sys" version = "0.1.0" -source = "git+https://github.com/rustdesk-org/kcp-sys#1e5e30ab8b8c2f7787ab0f88822de36476531562" +source = "git+https://github.com/rustdesk-org/kcp-sys#32a6c09fc6223f54aea83981a6aa8995931d29be" dependencies = [ "anyhow", "auto_impl", @@ -3660,6 +3660,7 @@ dependencies = [ "bytes", "cc", "dashmap 6.1.0", + "log", "parking_lot", "rand 0.8.5", "thiserror 2.0.11", diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 29b7601ca..4de0e7e32 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -77,6 +77,7 @@ pub struct Remote { video_threads: HashMap, chroma: Arc>>, last_record_state: bool, + sent_close_reason: bool, } #[derive(Default)] @@ -125,6 +126,7 @@ impl Remote { video_threads: Default::default(), chroma: Default::default(), last_record_state: false, + sent_close_reason: false, } } @@ -172,7 +174,7 @@ impl Remote { ) .await { - Ok(((mut peer, direct, pk, _kcp), (feedback, rendezvous_server))) => { + Ok(((mut peer, direct, pk, kcp), (feedback, rendezvous_server))) => { self.handler .connection_round_state .lock() @@ -320,6 +322,13 @@ impl Remote { if let Some(s) = self.stop_voice_call_sender.take() { s.send(()).ok(); } + if kcp.is_some() { + // Send the close reason if it hasn't been sent yet, as KCP cannot detect the socket close event. + self.send_close_reason(&mut peer, "kcp").await; + // KCP does not send messages immediately, so wait to ensure the last message is sent. + // 1ms works in my test, but 30ms is more reliable. + tokio::time::sleep(Duration::from_millis(30)).await; + } } Err(err) => { self.handler.on_establish_connection_error(err.to_string()); @@ -511,14 +520,22 @@ impl Remote { } } + async fn send_close_reason(&mut self, peer: &mut Stream, reason: &str) { + if self.sent_close_reason { + return; + } + let mut misc = Misc::new(); + misc.set_close_reason(reason.to_owned()); + let mut msg = Message::new(); + msg.set_misc(misc); + allow_err!(peer.send(&msg).await); + self.sent_close_reason = true; + } + async fn handle_msg_from_ui(&mut self, data: Data, peer: &mut Stream) -> bool { match data { Data::Close => { - let mut misc = Misc::new(); - misc.set_close_reason("".to_owned()); - let mut msg = Message::new(); - msg.set_misc(misc); - allow_err!(peer.send(&msg).await); + self.send_close_reason(peer, "").await; return false; } Data::Login((os_username, os_password, password, remember)) => { @@ -1712,6 +1729,7 @@ impl Remote { } } Some(misc::Union::CloseReason(c)) => { + self.sent_close_reason = true; // The controlled end will close, no need to send close reason self.handler.msgbox("error", "Connection Error", &c, ""); return false; } diff --git a/src/server/connection.rs b/src/server/connection.rs index e02b24918..01d84437d 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1937,6 +1937,16 @@ impl Connection { } async fn on_message(&mut self, msg: Message) -> bool { + if let Some(message::Union::Misc(misc)) = &msg.union { + // Move the CloseReason forward, as this message needs to be received when unauthorized, especially for kcp. + if let Some(misc::Union::CloseReason(s)) = &misc.union { + log::info!("receive close reason: {}", s); + self.on_close("Peer close", true).await; + raii::AuthedConnID::check_remove_session(self.inner.id(), self.session_key()); + return false; + } + } + // After handling CloseReason messages, proceed to process other message types if let Some(message::Union::LoginRequest(lr)) = msg.union { self.handle_login_request_without_validation(&lr).await; if self.authorized { @@ -2790,15 +2800,6 @@ impl Connection { Some(Instant::now().into()), ); } - Some(misc::Union::CloseReason(_)) => { - self.on_close("Peer close", true).await; - raii::AuthedConnID::check_remove_session( - self.inner.id(), - self.session_key(), - ); - return false; - } - Some(misc::Union::RestartRemoteDevice(_)) => { #[cfg(not(any(target_os = "android", target_os = "ios")))] if self.restart {