diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs index 551e5989a5..e5b50a9159 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -2033,7 +2033,7 @@ impl RdpServer { /// on, a preemption winner is indistinguishable from a normally-accepted /// connection. async fn serve_negotiated(&mut self, candidate: Box) -> ServerResult<()> { - self.display_suppressed.store(false, Ordering::Relaxed); + self.reset_connection_state(); let mut candidate = candidate; // Only NOW build the channel backends: this connection has @@ -2155,20 +2155,37 @@ impl RdpServer { result } + /// Per-connection state must start fresh for every client, on both the + /// normal path and a preemption winner. + fn reset_connection_state(&mut self) { + // If the previous client disconnected while it had sent + // `SuppressOutput { None }` (e.g., closed the mstsc window while + // minimized so the matching resume PDU never arrived), the flag would + // still read `true` here and the display backend would silently drop + // frames for the entire new session until/unless the new client + // happens to send a `RefreshRectangle` or `SuppressOutput { Some(rect) }`. + // Resetting here also covers backends that share an externally-created + // Arc via `set_display_suppressed_handle()`. + self.display_suppressed.store(false, Ordering::Relaxed); + + // Network characteristics describe one client's path. Kept across + // connections, the previous client's round trips would hold down this + // one's baseRTT (the lowest RTT seen, MS-RDPBCGR 2.2.14.1.5), its + // figures would be reported until overwritten, and a measurement it + // left pending would hold up the next one. + if self.autodetect.is_some() { + self.autodetect = Some(AutoDetectManager::new()); + } + self.autodetect_rtt.store(u32::MAX, Ordering::Relaxed); + self.autodetect_baseline_rtt.store(u32::MAX, Ordering::Relaxed); + self.autodetect_bandwidth.store(u32::MAX, Ordering::Relaxed); + } + async fn run_connection_inner(&mut self, stream: S, tls: TransportTls) -> ServerResult<()> where S: AsyncRead + AsyncWrite + Send + Sync + Unpin, { - // Per-connection state must start fresh: if the previous client - // disconnected while it had sent `SuppressOutput { None }` (e.g., - // closed the mstsc window while minimized so the matching resume - // PDU never arrived), the flag would still read `true` here and the - // display backend would silently drop frames for the entire new - // session until/unless the new client happens to send a - // `RefreshRectangle` or `SuppressOutput { Some(rect) }`. Resetting - // here also covers backends that share an externally-created Arc via - // `set_display_suppressed_handle()`. - self.display_suppressed.store(false, Ordering::Relaxed); + self.reset_connection_state(); let size = self.display.lock().await.size().await; let monitor_count = self.display.lock().await.monitor_count().await; diff --git a/crates/ironrdp-testsuite-core/tests/server/autodetect.rs b/crates/ironrdp-testsuite-core/tests/server/autodetect.rs index 8b1e6a6391..f4ab3976ba 100644 --- a/crates/ironrdp-testsuite-core/tests/server/autodetect.rs +++ b/crates/ironrdp-testsuite-core/tests/server/autodetect.rs @@ -595,6 +595,41 @@ fn with_autodetect_bandwidth_generation_handle_round_trips_the_same_arc() { ); } +/// A new connection does not inherit the previous one's network figures: the +/// handles read "not measured" again until this client has been measured. +#[tokio::test] +async fn a_new_connection_starts_with_no_measurements() { + use core::net::{Ipv4Addr, SocketAddr}; + use core::sync::atomic::Ordering; + + use ironrdp_server::RdpServer; + + let mut server = RdpServer::builder() + .with_addr(SocketAddr::from((Ipv4Addr::LOCALHOST, 0))) + .with_no_security() + .with_no_input() + .with_no_display() + .build(); + server.enable_autodetect(); + + // What an earlier connection left behind. + let rtt = server.autodetect_rtt_handle(); + let baseline_rtt = server.autodetect_baseline_rtt_handle(); + let bandwidth = server.autodetect_bandwidth_handle(); + rtt.store(12, Ordering::Relaxed); + baseline_rtt.store(3, Ordering::Relaxed); + bandwidth.store(80_000, Ordering::Relaxed); + + // A client that vanishes straight away still starts a connection. + let (client, server_side) = tokio::io::duplex(64); + drop(client); + let _ = server.run_connection(server_side).await; + + assert_eq!(rtt.load(Ordering::Relaxed), u32::MAX); + assert_eq!(baseline_rtt.load(Ordering::Relaxed), u32::MAX); + assert_eq!(bandwidth.load(Ordering::Relaxed), u32::MAX); +} + #[test] fn stale_probe_expiry() { let mut mgr = AutoDetectManager::new();