From 8e3c192d6579c5e52d60a4a1192264e1a31f9bfb Mon Sep 17 00:00:00 2001 From: Pedro Henrique Penna Date: Thu, 29 Oct 2026 17:56:28 -0700 Subject: [PATCH] runtime-adapter: let each connection to a guest listener open or fail reach_guest connected to a listener of the guest that OpenVMM publishes on host loopback with a 1 s timeout. Until OpenVMM publishes the listener, nothing listens at its port, and Windows answers the reset by sending the SYN again 0.5 s, 1.0 s, 1.5 s, and 2.0 s later before it fails the connection, so the attempts timed out instead, and the retry at 1.0 s landed on the timeout. When OpenVMM published the port between 0.5 s and 1.0 s into an attempt, that retry could complete the handshake just as connect_timeout gave up and closed its socket. OpenVMM accepted the connection all the same and relayed it to the guest, whose nc -l took it for its one client and ended with "listener 0", and the host's later attempts found no listener. That failed vm_layered allowed_host_loopback_publishes_the_listeners on Windows/WHP in CI run 40950585891, as #1033 describes. Each attempt now runs until it opens or fails, with TcpStream::connect. On loopback, a connection opens at once where something listens, and fails at once on Linux and after about 2 s on Windows where nothing does, so an attempt stays bounded and never leaves a connection behind. The copy of reach_guest in vm_clone.rs, which serves the listener checks of clones, gets the same change. On a Windows workstation, a replica of reach_guest against a listener that serves one client and appears at a random time failed with the incident's signature in 19 of 1,920 trials, and in none of 1,280 with TcpStream::connect. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/runtime-adapter/tests/vm_clone.rs | 7 +++++-- tests/runtime-adapter/tests/vm_layered.rs | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/runtime-adapter/tests/vm_clone.rs b/tests/runtime-adapter/tests/vm_clone.rs index 0de08ba29..34067bd60 100644 --- a/tests/runtime-adapter/tests/vm_clone.rs +++ b/tests/runtime-adapter/tests/vm_clone.rs @@ -4233,10 +4233,13 @@ fn a_clone_whose_mapping_fails_ends_before_its_sandbox_starts() { /// it did, or until `stop`, and returns whether it did: it reads the listener's greeting, answers /// it, closes its side, and reads to the end. A connection that ends without the greeting, as /// OpenVMM's do while the guest does not listen yet, or where it admits no connection of the host, -/// is retried. +/// is retried. Each attempt to connect runs until it opens or fails: Windows retries a loopback +/// connection that nothing accepts every half second for about two seconds, so an attempt that a +/// timeout gave up on could still open once OpenVMM publishes the listener, which would then take +/// that connection, already closed, for its one client. fn reach_guest(address: SocketAddr, stop: &AtomicBool) -> bool { while !stop.load(Ordering::SeqCst) { - if let Ok(mut stream) = TcpStream::connect_timeout(&address, Duration::from_secs(1)) { + if let Ok(mut stream) = TcpStream::connect(address) { stream .set_read_timeout(Some(Duration::from_secs(5))) .unwrap(); diff --git a/tests/runtime-adapter/tests/vm_layered.rs b/tests/runtime-adapter/tests/vm_layered.rs index 9fad1a949..00307e614 100644 --- a/tests/runtime-adapter/tests/vm_layered.rs +++ b/tests/runtime-adapter/tests/vm_layered.rs @@ -3011,10 +3011,14 @@ fn check_guest_listener(vm: &mut Vm<'_>, address: SocketAddr, reached: bool) { /// Tries to exchange a line each way with a listener of the guest through `address`, until it did, /// or until `stop`, and returns whether it did: it reads the listener's greeting, answers it, /// closes its side, and reads to the end. A connection that ends without the greeting, as -/// OpenVMM's do while the guest does not listen yet, is retried. +/// OpenVMM's do while the guest does not listen yet, is retried. Each attempt to connect runs +/// until it opens or fails: Windows retries a loopback connection that nothing accepts every half +/// second for about two seconds, so an attempt that a timeout gave up on could still open once +/// OpenVMM publishes the listener, which would then take that connection, already closed, for its +/// one client. fn reach_guest(address: SocketAddr, stop: &AtomicBool) -> bool { while !stop.load(Ordering::SeqCst) { - if let Ok(mut stream) = TcpStream::connect_timeout(&address, Duration::from_secs(1)) { + if let Ok(mut stream) = TcpStream::connect(address) { stream .set_read_timeout(Some(Duration::from_secs(5))) .unwrap();