diff --git a/Cargo.lock b/Cargo.lock index 0e9f071d1..58c9eb006 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4280,7 +4280,7 @@ dependencies = [ [[package]] name = "kcp-sys" version = "0.1.0" -source = "git+https://github.com/rustdesk-org/kcp-sys?branch=rustdesk-patches#023a0065398968989f2ddfcf5cc72bb886d02675" +source = "git+https://github.com/rustdesk-org/kcp-sys?branch=rustdesk-patches#938eda3e5e9757a612385503af7a6cb1189b2cdd" dependencies = [ "anyhow", "auto_impl", diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index cf8bce00d..0de142374 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -16,18 +16,22 @@ use crate::{ const RESTART_REMOTE_DEVICE_NO_DATA_TIMEOUT: Duration = Duration::from_secs(5); const KCP_CLOSE_REASON_FLUSH_DELAY: Duration = Duration::from_millis(30); // A peer that is killed, logged out or rebooted sends nothing at all over UDP, so the session -// sees silence and only a timeout ends it. This bounds that wait for WebRTC, which has a liveness -// signal of its own; TCP and WebSocket are left exactly as they were, and the 30s timeout below -// still backs all of them. +// sees silence and only a timeout ends it. These bound that wait for the two transports that +// have a liveness signal of their own; TCP and WebSocket are left exactly as they were, and the +// 30s timeout below still backs all of them. // -// Not a hard upper bound on how soon this notices: sends are awaited inline in this same loop, so -// one in progress keeps the tick below from running, capped by the transport's own send timeout. -// Removing that needs the framing work that would stop one message owning the link. +// Neither is a hard upper bound on how soon this notices: sends are awaited inline in this same +// loop, so one in progress keeps the tick below from running. WebRTC caps that at its existing +// send timeout; KCP sets none, so a large message can delay the check by however long it takes +// to drain. Removing that needs the framing work that would stop one message owning the link. // // Grace after ICE reports Disconnected, which it does ~5s after it stops hearing from the peer, // for ~8s in total. Disconnected is transient by design, so this waits out a Wi-Fi roam or a // sleep/wake rather than acting on the first hint. const WEBRTC_SUSPECT_GRACE: Duration = Duration::from_secs(3); +// KCP has no equivalent hint, only how long since a packet last arrived; its endpoint pings an +// idle peer about every 2s, so this is several missed pings. +const KCP_PEER_SILENCE_LIMIT: Duration = Duration::from_secs(8); #[cfg(feature = "unix-file-copy-paste")] use crate::{clipboard::try_empty_clipboard_files, clipboard_file::unix_file_clip}; use base::{ @@ -339,7 +343,11 @@ impl Remote { webrtc_suspect_since = None; } let peer_gone = webrtc_suspect_since - .map_or(false, |since| since.elapsed() >= WEBRTC_SUSPECT_GRACE); + .map_or(false, |since| since.elapsed() >= WEBRTC_SUSPECT_GRACE) + || kcp + .as_ref() + .and_then(|k| k.peer_silent_for()) + .map_or(false, |silent| silent >= KCP_PEER_SILENCE_LIMIT); if peer_gone { log::info!("Peer stopped answering, reconnecting"); self.handler.msgbox("restarting-show", "Connecting...", "Connection in progress. Please wait.", ""); diff --git a/src/kcp_stream.rs b/src/kcp_stream.rs index 2c4cfe4bd..50a00296f 100644 --- a/src/kcp_stream.rs +++ b/src/kcp_stream.rs @@ -8,14 +8,15 @@ use hbb_common::{ tokio_util, ResultType, Stream, }; use kcp_sys::{ - endpoint::KcpEndpoint, + endpoint::{ConnId, KcpEndpoint}, packet_def::{KcpPacket, KcpPacketHeader}, stream, }; use std::{net::SocketAddr, sync::Arc}; pub struct KcpStream { - _endpoint: KcpEndpoint, + endpoint: KcpEndpoint, + conn_id: ConnId, stop_sender: Option>, } @@ -41,6 +42,14 @@ impl KcpStream { } } + /// How long since a valid packet was last received from the peer, or `None` once the + /// connection is gone. Answered by the KCP endpoint's own tasks, not by the session's read + /// loop, so it stays meaningful while that loop is busy sending a large message; and the + /// endpoint pings an idle peer often enough that silence here means the peer, not quiet. + pub fn peer_silent_for(&self) -> Option { + self.endpoint.peer_silent_for(&self.conn_id) + } + fn create_framed(stream: stream::KcpStream, local_addr: Option) -> Stream { Stream::Tcp(FramedStream( tokio_util::codec::Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()), @@ -77,7 +86,8 @@ impl KcpStream { if let Some(stream) = stream::KcpStream::new(&endpoint, conn_id) { Ok(( Self { - _endpoint: endpoint, + endpoint, + conn_id, stop_sender: Some(stop_sender), }, Self::create_framed(stream, udp_socket.local_addr().ok()), @@ -108,7 +118,8 @@ impl KcpStream { if let Some(stream) = stream::KcpStream::new(&endpoint, conn_id) { Ok(( Self { - _endpoint: endpoint, + endpoint, + conn_id, stop_sender: Some(stop_sender), }, Self::create_framed(stream, udp_socket.local_addr().ok()),